mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-13 07:45:41 +08:00
feat(ums): 银联商务通道商户管理(管理端)
- 新增银联商务通道商户管理页(umsManage)与创建配置页调整 - api 补充通道商户接口, 新增 umsManage locale 中英文 - ChannelMerchantDetailDispatch 分发银联商务系列产品至管理页
This commit is contained in:
@@ -1,15 +1,119 @@
|
||||
import type { Result } from '#/types/web';
|
||||
import type { MchEntity, Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 银联商务通道商户创建 API
|
||||
* 银联商务直连通道商户 API
|
||||
*/
|
||||
export const ChannelMerchantUmsApi = {
|
||||
export const UmsDirectChannelMerchantApi = {
|
||||
/**
|
||||
* 创建银联商务通道商户
|
||||
* 创建银联商务直连通道商户(同时保存密钥配置)
|
||||
*/
|
||||
create(data: Record<string, any>): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/ums/channel-merchant/create', data });
|
||||
create(data: UmsDirectChannelMerchantCreateParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/ums/direct-channel-merchant/create', data });
|
||||
},
|
||||
/**
|
||||
* 根据通道商户号查询银联商务直连通道商户配置
|
||||
*/
|
||||
findByChannelMchNo(channelMchNo: string): Promise<Result<UmsDirectChannelMerchantConfig>> {
|
||||
return defHttp.get({
|
||||
url: '/admin/ums/direct-channel-merchant/find-by-channel-mch-no',
|
||||
params: { channelMchNo },
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 根据通道商户号查询密钥配置
|
||||
*/
|
||||
findKeyConfig(channelMchNo: string): Promise<Result<UmsDirectKeyConfig>> {
|
||||
return defHttp.get({
|
||||
url: '/admin/ums/direct-channel-merchant/find-key-config',
|
||||
params: { channelMchNo },
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 保存密钥配置
|
||||
*/
|
||||
saveKeyConfig(data: UmsDirectKeyConfigParam): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/admin/ums/direct-channel-merchant/save-key-config', data });
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 银联商务直连通道商户配置
|
||||
*/
|
||||
export interface UmsDirectChannelMerchantConfig extends MchEntity {
|
||||
/** 通道商户号(系统生成雪花号) */
|
||||
channelMchNo?: string;
|
||||
/** 所属支付产品 */
|
||||
product?: string;
|
||||
/** 银联商务商户号(mid) */
|
||||
merchantNo?: string;
|
||||
/** 终端号(tid) */
|
||||
terminalNo?: string;
|
||||
/** 订单号前缀 */
|
||||
orderPrefix?: string;
|
||||
/** 是否沙箱环境 */
|
||||
sandbox?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 银联商务直连密钥配置
|
||||
*/
|
||||
export interface UmsDirectKeyConfig {
|
||||
/** 通道商户号 */
|
||||
channelMchNo?: string;
|
||||
/** 商户号 */
|
||||
mchNo?: string;
|
||||
/** 银联商务应用 AppId */
|
||||
umsAppId?: string;
|
||||
/** 应用密钥(HmacSHA256 签名密钥) */
|
||||
appKey?: string;
|
||||
/** 通讯密钥(回调验签密钥) */
|
||||
secretKey?: string;
|
||||
/** 应用密钥是否已配置 */
|
||||
appKeyConfigured?: boolean;
|
||||
/** 通讯密钥是否已配置 */
|
||||
secretKeyConfigured?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 银联商务直连密钥配置保存参数
|
||||
*/
|
||||
export interface UmsDirectKeyConfigParam {
|
||||
/** 通道商户号 */
|
||||
channelMchNo: string;
|
||||
/** 商户号 */
|
||||
mchNo?: string;
|
||||
/** 银联商务应用 AppId */
|
||||
umsAppId?: string;
|
||||
/** 应用密钥 */
|
||||
appKey?: string;
|
||||
/** 通讯密钥 */
|
||||
secretKey?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 银联商务直连通道商户创建参数
|
||||
*/
|
||||
export interface UmsDirectChannelMerchantCreateParam {
|
||||
/** 商户号 */
|
||||
mchNo: string;
|
||||
/** 通道商户名称 */
|
||||
channelMerchantName: string;
|
||||
/** 所属支付产品 */
|
||||
product: string;
|
||||
/** 银联商务商户号 */
|
||||
merchantNo: string;
|
||||
/** 终端号 */
|
||||
terminalNo?: string;
|
||||
/** 订单号前缀 */
|
||||
orderPrefix?: string;
|
||||
/** 是否沙箱环境 */
|
||||
sandbox?: boolean;
|
||||
/** 银联商务应用 AppId */
|
||||
umsAppId: string;
|
||||
/** 应用密钥 */
|
||||
appKey: string;
|
||||
/** 通讯密钥 */
|
||||
secretKey: string;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"manageTitle": "UMS Direct Merchant Management",
|
||||
"cardDirectKeyConfig": "Key Config",
|
||||
"cardDirectKeyConfigDesc": "Configure UMS merchant-level App ID and keys",
|
||||
"directKeyConfigTitle": "UMS Key Configuration",
|
||||
"keyConfigSection": "Merchant Key Config",
|
||||
"appKeyConfigured": "Configured (enter new value to change)",
|
||||
"secretKeyConfigured": "Configured (enter new value to change)"
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"manageTitle": "银联商务直连商户管理",
|
||||
"cardDirectKeyConfig": "密钥配置",
|
||||
"cardDirectKeyConfigDesc": "配置银联商务商户维度的应用ID和密钥",
|
||||
"directKeyConfigTitle": "银联商务密钥配置",
|
||||
"keyConfigSection": "商户密钥配置",
|
||||
"appKeyConfigured": "已配置(如需修改请直接输入新值)",
|
||||
"secretKeyConfigured": "已配置(如需修改请直接输入新值)"
|
||||
}
|
||||
@@ -1,195 +1,187 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
import type { UmsDirectChannelMerchantCreateParam } from '#/api/payment/channel/ums/channel-merchant.api';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { IconifyIcon } from '@vben-core/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ChannelMerchantUmsApi } from '#/api/payment/channel/ums/channel-merchant.api';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
import { IconifyIcon } from '@vben-core/icons';
|
||||
|
||||
defineOptions({ name: 'UmsMchCreateConfig' });
|
||||
import { UmsDirectChannelMerchantApi } from '#/api/payment/channel/ums/channel-merchant.api';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'prev'): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
defineOptions({ name: 'UmsMchCreateConfig' });
|
||||
|
||||
const { message } = useMessage();
|
||||
const emit = defineEmits<{
|
||||
(e: 'prev'): void;
|
||||
(e: 'close'): void;
|
||||
}>();
|
||||
|
||||
const mchNo = ref('');
|
||||
const productCode = ref('');
|
||||
const productDisplayName = ref('');
|
||||
const formRef = ref();
|
||||
const form = ref({
|
||||
channelMerchantName: '',
|
||||
sandbox: false,
|
||||
umsAppId: '',
|
||||
appKey: '',
|
||||
merchantNo: '',
|
||||
terminalNo: '',
|
||||
orderPrefix: '',
|
||||
secretKey: '',
|
||||
});
|
||||
const { message } = useMessage();
|
||||
|
||||
const visible = ref(false);
|
||||
const createSuccess = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
const rules = computed(() => ({
|
||||
channelMerchantName: [{ required: true, message: $t('payment.merchant.channelMerchant.channelMerchantNameRequired') }],
|
||||
umsAppId: [{ required: true, message: $t('payment.channel.ums.validation.umsAppId') }],
|
||||
appKey: [{ required: true, message: $t('payment.channel.ums.validation.appKey') }],
|
||||
merchantNo: [{ required: true, message: $t('payment.channel.ums.validation.merchantNo') }],
|
||||
terminalNo: [{ required: true, message: $t('payment.channel.ums.validation.terminalNo') }],
|
||||
orderPrefix: [{ required: true, message: $t('payment.channel.ums.validation.orderPrefix') }],
|
||||
secretKey: [{ required: true, message: $t('payment.channel.ums.validation.secretKey') }],
|
||||
}));
|
||||
|
||||
function init(no: string, product: string, displayName: string) {
|
||||
mchNo.value = no;
|
||||
productCode.value = product;
|
||||
productDisplayName.value = displayName;
|
||||
visible.value = true;
|
||||
createSuccess.value = false;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function validate(): boolean {
|
||||
let valid = false;
|
||||
formRef.value?.validate((errors: any) => {
|
||||
valid = !errors;
|
||||
const mchNo = ref('');
|
||||
const productCode = ref('');
|
||||
const productDisplayName = ref('');
|
||||
const formRef = ref();
|
||||
const form = ref({
|
||||
channelMerchantName: '',
|
||||
sandbox: false,
|
||||
umsAppId: '',
|
||||
appKey: '',
|
||||
merchantNo: '',
|
||||
terminalNo: '',
|
||||
orderPrefix: '',
|
||||
secretKey: '',
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
|
||||
function getData(): Record<string, any> {
|
||||
return { ...form.value };
|
||||
}
|
||||
const visible = ref(false);
|
||||
const createSuccess = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
|
||||
function submit(param: Record<string, any>): Promise<any> {
|
||||
return ChannelMerchantUmsApi.create({
|
||||
...param,
|
||||
...form.value,
|
||||
});
|
||||
}
|
||||
const rules = computed(() => ({
|
||||
channelMerchantName: [
|
||||
{ required: true, message: $t('payment.merchant.channelMerchant.channelMerchantNameRequired') },
|
||||
],
|
||||
umsAppId: [{ required: true, message: $t('payment.channel.ums.validation.umsAppId') }],
|
||||
appKey: [{ required: true, message: $t('payment.channel.ums.validation.appKey') }],
|
||||
merchantNo: [{ required: true, message: $t('payment.channel.ums.validation.merchantNo') }],
|
||||
terminalNo: [{ required: true, message: $t('payment.channel.ums.validation.terminalNo') }],
|
||||
orderPrefix: [{ required: true, message: $t('payment.channel.ums.validation.orderPrefix') }],
|
||||
secretKey: [{ required: true, message: $t('payment.channel.ums.validation.secretKey') }],
|
||||
}));
|
||||
|
||||
function handlePrev() {
|
||||
emit('prev');
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value?.validate().then(() => {
|
||||
submitLoading.value = true;
|
||||
const param = {
|
||||
mchNo: mchNo.value,
|
||||
product: productCode.value,
|
||||
channel: 'ums',
|
||||
...form.value,
|
||||
};
|
||||
submit(param)
|
||||
.then(() => {
|
||||
createSuccess.value = true;
|
||||
message.success($t('payment.merchant.channelMerchant.createSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
}).catch(() => {});
|
||||
function init(no: string, product: string, displayName: string) {
|
||||
mchNo.value = no;
|
||||
productCode.value = product;
|
||||
productDisplayName.value = displayName;
|
||||
visible.value = true;
|
||||
createSuccess.value = false;
|
||||
resetForm();
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
nextTick(() => {
|
||||
formRef.value?.resetFields();
|
||||
});
|
||||
}
|
||||
function validate(): boolean {
|
||||
let valid = false;
|
||||
formRef.value?.validate((errors: any) => {
|
||||
valid = !errors;
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
|
||||
defineExpose({ init, validate, getData, submit });
|
||||
function getData(): Record<string, any> {
|
||||
return { ...form.value };
|
||||
}
|
||||
|
||||
function submit(param: Record<string, any>): Promise<any> {
|
||||
return UmsDirectChannelMerchantApi.create({
|
||||
...param,
|
||||
...form.value,
|
||||
} as UmsDirectChannelMerchantCreateParam);
|
||||
}
|
||||
|
||||
function handlePrev() {
|
||||
emit('prev');
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
submitLoading.value = true;
|
||||
const param = {
|
||||
mchNo: mchNo.value,
|
||||
product: productCode.value,
|
||||
channel: 'ums',
|
||||
...form.value,
|
||||
};
|
||||
submit(param)
|
||||
.then(() => {
|
||||
createSuccess.value = true;
|
||||
message.success($t('payment.merchant.channelMerchant.createSuccess'));
|
||||
})
|
||||
.finally(() => {
|
||||
submitLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
nextTick(() => {
|
||||
formRef.value?.resetFields();
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({ init, validate, getData, submit });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="visible">
|
||||
<div v-if="!createSuccess">
|
||||
<a-spin :spinning="submitLoading">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
:validate-trigger="['blur', 'change']"
|
||||
>
|
||||
<!-- 国际化:商户名称 -->
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.channelMerchantName')" name="channelMerchantName">
|
||||
<a-input
|
||||
v-model:value="form.channelMerchantName"
|
||||
:placeholder="$t('payment.merchant.channelMerchant.pleaseInputName')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 国际化:所属支付产品 -->
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.selectedProduct')">
|
||||
<a-input :value="productDisplayName" disabled />
|
||||
</a-form-item>
|
||||
<!-- 国际化:沙箱环境 -->
|
||||
<a-form-item :label="$t('payment.channel.ums.sandbox')" name="sandbox">
|
||||
<a-switch
|
||||
v-model:checked="form.sandbox"
|
||||
:checked-children="$t('common.yes')"
|
||||
:un-checked-children="$t('common.no')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 16 }"
|
||||
:validate-trigger="['blur', 'change']"
|
||||
>
|
||||
<!-- 国际化:商户名称 -->
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.channelMerchantName')" name="channelMerchantName">
|
||||
<a-input
|
||||
v-model:value="form.channelMerchantName"
|
||||
:placeholder="$t('payment.merchant.channelMerchant.pleaseInputName')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 国际化:所属支付产品 -->
|
||||
<a-form-item :label="$t('payment.merchant.channelMerchant.selectedProduct')">
|
||||
<a-input :value="productDisplayName" disabled />
|
||||
</a-form-item>
|
||||
<!-- 国际化:沙箱环境 -->
|
||||
<a-form-item :label="$t('payment.channel.ums.sandbox')" name="sandbox">
|
||||
<a-switch
|
||||
v-model:checked="form.sandbox"
|
||||
:checked-children="$t('common.yes')"
|
||||
:un-checked-children="$t('common.no')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('payment.channel.ums.umsAppId')" name="umsAppId">
|
||||
<a-input
|
||||
v-model:value="form.umsAppId"
|
||||
:placeholder="$t('payment.channel.ums.umsAppIdPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.channel.ums.umsAppId')" name="umsAppId">
|
||||
<a-input v-model:value="form.umsAppId" :placeholder="$t('payment.channel.ums.umsAppIdPlaceholder')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('payment.channel.ums.appKey')" name="appKey">
|
||||
<a-input-password
|
||||
v-model:value="form.appKey"
|
||||
:placeholder="$t('payment.channel.ums.appKeyPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.channel.ums.appKey')" name="appKey">
|
||||
<a-input-password v-model:value="form.appKey" :placeholder="$t('payment.channel.ums.appKeyPlaceholder')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('payment.channel.ums.merchantNo')" name="merchantNo">
|
||||
<a-input
|
||||
v-model:value="form.merchantNo"
|
||||
:placeholder="$t('payment.channel.ums.merchantNoPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.channel.ums.merchantNo')" name="merchantNo">
|
||||
<a-input v-model:value="form.merchantNo" :placeholder="$t('payment.channel.ums.merchantNoPlaceholder')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('payment.channel.ums.terminalNo')" name="terminalNo">
|
||||
<a-input
|
||||
v-model:value="form.terminalNo"
|
||||
:placeholder="$t('payment.channel.ums.terminalNoPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.channel.ums.terminalNo')" name="terminalNo">
|
||||
<a-input v-model:value="form.terminalNo" :placeholder="$t('payment.channel.ums.terminalNoPlaceholder')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('payment.channel.ums.orderPrefix')" name="orderPrefix">
|
||||
<a-input
|
||||
v-model:value="form.orderPrefix"
|
||||
:placeholder="$t('payment.channel.ums.orderPrefixPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.channel.ums.orderPrefix')" name="orderPrefix">
|
||||
<a-input v-model:value="form.orderPrefix" :placeholder="$t('payment.channel.ums.orderPrefixPlaceholder')" />
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item :label="$t('payment.channel.ums.secretKey')" name="secretKey">
|
||||
<a-input-password
|
||||
v-model:value="form.secretKey"
|
||||
:placeholder="$t('payment.channel.ums.secretKeyPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="$t('payment.channel.ums.secretKey')" name="secretKey">
|
||||
<a-input-password
|
||||
v-model:value="form.secretKey"
|
||||
:placeholder="$t('payment.channel.ums.secretKeyPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<div class="flex justify-center gap-4 mt-8 pt-6 border-t border-border">
|
||||
<a-button @click="handlePrev">
|
||||
{{ $t('payment.merchant.channelMerchant.prevStep') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
{{ $t('payment.merchant.channelMerchant.create') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
<div class="flex justify-center gap-4 mt-8 pt-6 border-t border-border">
|
||||
<a-button @click="handlePrev">
|
||||
{{ $t('payment.merchant.channelMerchant.prevStep') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="submitLoading" @click="handleSubmit">
|
||||
{{ $t('payment.merchant.channelMerchant.create') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { type ChannelMerchantResult } from '#/api/payment/channel/channel-merchant.api';
|
||||
import {
|
||||
UmsDirectChannelMerchantApi,
|
||||
type UmsDirectChannelMerchantConfig,
|
||||
} from '#/api/payment/channel/ums/channel-merchant.api';
|
||||
|
||||
defineOptions({ name: 'UmsDirectChannelMerchantBasicInfo' });
|
||||
|
||||
const props = defineProps<{
|
||||
channelMchNo: string;
|
||||
channelMerchant: ChannelMerchantResult;
|
||||
}>();
|
||||
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const config = ref<UmsDirectChannelMerchantConfig>({});
|
||||
|
||||
const enableLabel = computed(() =>
|
||||
props.channelMerchant.enable
|
||||
? $t('payment.merchant.channelMerchant.enableStatus')
|
||||
: $t('payment.merchant.channelMerchant.disableStatus'),
|
||||
);
|
||||
|
||||
const sourceLabel = computed(() => {
|
||||
if (props.channelMerchant.source === 'manual') {
|
||||
return $t('payment.merchant.channelMerchant.sourceManual');
|
||||
}
|
||||
return props.channelMerchant.source || '-';
|
||||
});
|
||||
|
||||
const sandboxLabel = computed(() => (config.value.sandbox ? $t('common.yes') : $t('common.no')));
|
||||
|
||||
function loadConfig() {
|
||||
if (!props.channelMchNo) {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
config.value = {};
|
||||
|
||||
UmsDirectChannelMerchantApi.findByChannelMchNo(props.channelMchNo)
|
||||
.then(({ data }) => {
|
||||
config.value = data || {};
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function open() {
|
||||
visible.value = true;
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.channelMchNo,
|
||||
() => {
|
||||
if (visible.value) {
|
||||
loadConfig();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
defineExpose({ open, close });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:open="visible"
|
||||
:title="$t('payment.merchant.channelMerchant.basicInfoDrawerTitle')"
|
||||
:size="640"
|
||||
destroy-on-hidden
|
||||
>
|
||||
<a-spin :spinning="loading">
|
||||
<a-descriptions bordered :column="1" size="small">
|
||||
<!-- 国际化: 通道商户号 -->
|
||||
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.channelMerchantNo')">
|
||||
{{ channelMerchant.channelMchNo || '-' }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 通道商户名称 -->
|
||||
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.channelMerchantName')">
|
||||
{{ channelMerchant.channelMerchantName || '-' }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 是否启用 -->
|
||||
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.enable')">
|
||||
{{ enableLabel }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 来源 -->
|
||||
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.source')">
|
||||
{{ sourceLabel }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 银联商务商户号 -->
|
||||
<a-descriptions-item :label="$t('payment.channel.ums.merchantNo')">
|
||||
{{ config.merchantNo || '-' }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 终端号 -->
|
||||
<a-descriptions-item :label="$t('payment.channel.ums.terminalNo')">
|
||||
{{ config.terminalNo || '-' }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 订单号前缀 -->
|
||||
<a-descriptions-item :label="$t('payment.channel.ums.orderPrefix')">
|
||||
{{ config.orderPrefix || '-' }}
|
||||
</a-descriptions-item>
|
||||
<!-- 国际化: 沙箱环境 -->
|
||||
<a-descriptions-item :label="$t('payment.channel.ums.sandbox')">
|
||||
{{ sandboxLabel }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-spin>
|
||||
</a-drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,159 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, nextTick, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { UmsDirectChannelMerchantApi, type UmsDirectKeyConfig } from '#/api/payment/channel/ums/channel-merchant.api';
|
||||
import { PermCodes } from '#/constants/perm-codes';
|
||||
import { useFormEdit } from '#/hooks/useFormEdit';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
import { usePermission } from '#/hooks/usePermission';
|
||||
|
||||
defineOptions({ name: 'UmsDirectKeyConfigEdit' });
|
||||
|
||||
const props = defineProps<{
|
||||
channelMchNo: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'saved'): void;
|
||||
}>();
|
||||
|
||||
const { labelCol, wrapperCol, confirmLoading, visible, handleCancel, diffForm } = useFormEdit();
|
||||
|
||||
const { message } = useMessage();
|
||||
const { hasPermission } = usePermission();
|
||||
|
||||
const formRef = ref();
|
||||
const form = ref<UmsDirectKeyConfig>({} as UmsDirectKeyConfig);
|
||||
let rawForm: Record<string, any> = {};
|
||||
|
||||
const canEdit = computed(() => hasPermission(PermCodes.Channel.Merchant.MANAGE));
|
||||
|
||||
const drawerTitle = $t('payment.channel.umsManage.directKeyConfigTitle');
|
||||
|
||||
const rules = {
|
||||
umsAppId: [{ required: true, message: $t('payment.channel.ums.validation.umsAppId') }],
|
||||
appKey: [{ required: true, message: $t('payment.channel.ums.validation.appKey') }],
|
||||
secretKey: [{ required: true, message: $t('payment.channel.ums.validation.secretKey') }],
|
||||
};
|
||||
|
||||
/** 打开抽屉并加载密钥配置 */
|
||||
function init() {
|
||||
visible.value = true;
|
||||
resetForm();
|
||||
loadConfig();
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
if (!props.channelMchNo) return;
|
||||
confirmLoading.value = true;
|
||||
UmsDirectChannelMerchantApi.findKeyConfig(props.channelMchNo)
|
||||
.then(({ data }) => {
|
||||
rawForm = { ...data };
|
||||
form.value = { ...data } as UmsDirectKeyConfig;
|
||||
})
|
||||
.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function handleOk() {
|
||||
formRef.value
|
||||
?.validate()
|
||||
.then(() => {
|
||||
confirmLoading.value = true;
|
||||
UmsDirectChannelMerchantApi.saveKeyConfig({
|
||||
...form.value,
|
||||
...diffForm(rawForm, form.value, 'appKey', 'secretKey'),
|
||||
channelMchNo: props.channelMchNo,
|
||||
})
|
||||
.then(() => {
|
||||
message.success($t('common.saveSuccess'));
|
||||
handleCancel();
|
||||
emit('saved');
|
||||
})
|
||||
.finally(() => {
|
||||
confirmLoading.value = false;
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
nextTick(() => {
|
||||
formRef.value?.resetFields();
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({ init });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-drawer
|
||||
v-model:open="visible"
|
||||
:title="drawerTitle"
|
||||
size="large"
|
||||
:styles="{ footer: { textAlign: 'right' } }"
|
||||
:mask-closable="false"
|
||||
destroy-on-hidden
|
||||
@close="handleCancel"
|
||||
>
|
||||
<a-spin :spinning="confirmLoading">
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="labelCol"
|
||||
:wrapper-col="wrapperCol"
|
||||
:validate-trigger="['blur', 'change']"
|
||||
>
|
||||
<a-divider orientation="left">{{ $t('payment.channel.umsManage.keyConfigSection') }}</a-divider>
|
||||
|
||||
<!-- 国际化: 应用ID -->
|
||||
<a-form-item :label="$t('payment.channel.ums.umsAppId')" name="umsAppId">
|
||||
<a-input
|
||||
v-model:value="form.umsAppId"
|
||||
:disabled="!canEdit"
|
||||
:placeholder="$t('payment.channel.ums.umsAppIdPlaceholder')"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 国际化: 应用密钥 -->
|
||||
<a-form-item :label="$t('payment.channel.ums.appKey')" name="appKey">
|
||||
<a-input-password
|
||||
v-model:value="form.appKey"
|
||||
:disabled="!canEdit"
|
||||
:placeholder="
|
||||
form.appKeyConfigured
|
||||
? $t('payment.channel.umsManage.appKeyConfigured')
|
||||
: $t('payment.channel.ums.appKeyPlaceholder')
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<!-- 国际化: 通讯密钥 -->
|
||||
<a-form-item :label="$t('payment.channel.ums.secretKey')" name="secretKey">
|
||||
<a-input-password
|
||||
v-model:value="form.secretKey"
|
||||
:disabled="!canEdit"
|
||||
:placeholder="
|
||||
form.secretKeyConfigured
|
||||
? $t('payment.channel.umsManage.secretKeyConfigured')
|
||||
: $t('payment.channel.ums.secretKeyPlaceholder')
|
||||
"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
|
||||
<template #footer>
|
||||
<a-space>
|
||||
<a-button @click="handleCancel">{{ $t('common.cancel') }}</a-button>
|
||||
<a-button v-if="canEdit" type="primary" :loading="confirmLoading" @click="handleOk">
|
||||
{{ $t('common.save') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-drawer>
|
||||
</template>
|
||||
@@ -0,0 +1,171 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ChannelMerchantResult } from '#/api/payment/channel/channel-merchant.api';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { IconifyIcon } from '@vben-core/icons';
|
||||
|
||||
import UmsDirectChannelMerchantBasicInfo from './UmsDirectChannelMerchantBasicInfo.vue';
|
||||
import UmsDirectKeyConfigEdit from './UmsDirectKeyConfigEdit.vue';
|
||||
|
||||
defineOptions({ name: 'UmsDirectMchManage' });
|
||||
|
||||
const loading = ref(false);
|
||||
const mchNo = ref('');
|
||||
const channelMchNo = ref('');
|
||||
const channelMerchant = ref<ChannelMerchantResult>({});
|
||||
const basicInfoRef = ref<InstanceType<typeof UmsDirectChannelMerchantBasicInfo>>();
|
||||
const keyConfigRef = ref<InstanceType<typeof UmsDirectKeyConfigEdit>>();
|
||||
|
||||
/** 功能卡片配置(银联商务首期仅基本信息与密钥配置, 无应用管理) */
|
||||
const functionCards = computed(() => [
|
||||
{
|
||||
// 国际化: 基础管理
|
||||
group: $t('payment.merchant.channelMerchant.groupBasic'),
|
||||
color: 'blue',
|
||||
cards: [
|
||||
{
|
||||
key: 'basicInfo',
|
||||
// 国际化: 基本信息
|
||||
title: $t('payment.merchant.channelMerchant.cardBasicInfo'),
|
||||
icon: 'ant-design:info-circle-outlined',
|
||||
// 国际化: 编辑通道商户基本信息
|
||||
description: $t('payment.merchant.channelMerchant.cardBasicInfoDesc'),
|
||||
},
|
||||
{
|
||||
key: 'keyConfig',
|
||||
// 国际化: 密钥配置
|
||||
title: $t('payment.channel.umsManage.cardDirectKeyConfig'),
|
||||
icon: 'ant-design:key-outlined',
|
||||
// 国际化: 配置通道商户密钥
|
||||
description: $t('payment.channel.umsManage.cardDirectKeyConfigDesc'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
/** 获取组主题颜色(底条) */
|
||||
function getGroupColorClass(color: string) {
|
||||
const map: Record<string, string> = {
|
||||
blue: 'bg-blue-500',
|
||||
green: 'bg-emerald-500',
|
||||
purple: 'bg-purple-500',
|
||||
};
|
||||
return map[color] || 'bg-gray-500';
|
||||
}
|
||||
|
||||
/** 获取图标背景颜色 */
|
||||
function getIconBgClass(color: string) {
|
||||
const map: Record<string, string> = {
|
||||
blue: 'bg-primary/10 text-primary',
|
||||
green: 'bg-success/10 text-success',
|
||||
purple: 'bg-purple-500/10 text-purple-500',
|
||||
};
|
||||
return map[color] || 'bg-muted text-muted-foreground';
|
||||
}
|
||||
|
||||
function init(no: string, cMchNo: string, summary: ChannelMerchantResult) {
|
||||
mchNo.value = no;
|
||||
channelMchNo.value = cMchNo;
|
||||
channelMerchant.value = summary;
|
||||
}
|
||||
|
||||
function handleCardClick(card: { key: string }) {
|
||||
if (card.key === 'basicInfo') {
|
||||
basicInfoRef.value?.open();
|
||||
}
|
||||
if (card.key === 'keyConfig') {
|
||||
keyConfigRef.value?.init();
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ init });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="space-y-12 py-4">
|
||||
<div v-for="group in functionCards" :key="group.group">
|
||||
<div class="mb-6 flex items-center gap-3 px-2">
|
||||
<div class="h-6 w-1.5 rounded-full shadow-sm" :class="getGroupColorClass(group.color)"></div>
|
||||
<span class="text-xl font-extrabold tracking-tight text-foreground">{{ group.group }}</span>
|
||||
</div>
|
||||
<div class="card-grid">
|
||||
<a-card
|
||||
v-for="card in group.cards"
|
||||
:key="card.key"
|
||||
hoverable
|
||||
class="isv-card group relative overflow-hidden rounded-2xl border-none bg-card shadow-md transition-all duration-300 hover:-translate-y-1.5 hover:shadow-xl"
|
||||
:styles="{ body: { padding: '24px 20px' } }"
|
||||
@click="handleCardClick(card)"
|
||||
>
|
||||
<div class="flex flex-col items-center text-center">
|
||||
<div
|
||||
class="mb-4 flex h-14 w-14 items-center justify-center rounded-2xl shadow-sm transition-all duration-300 group-hover:scale-110 group-hover:shadow-md"
|
||||
:class="getIconBgClass(group.color)"
|
||||
>
|
||||
<IconifyIcon :icon="card.icon" class="h-7 w-7" />
|
||||
</div>
|
||||
<div
|
||||
class="mb-1.5 text-base font-bold text-foreground group-hover:text-primary transition-colors duration-300"
|
||||
>
|
||||
{{ card.title }}
|
||||
</div>
|
||||
<a-tooltip :title="card.description" placement="bottom">
|
||||
<div
|
||||
class="card-desc line-clamp-1 text-xs leading-relaxed text-muted-foreground group-hover:text-foreground transition-colors duration-300"
|
||||
>
|
||||
{{ card.description }}
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<div
|
||||
class="absolute bottom-0 left-0 h-1.5 w-0 transition-all duration-300 group-hover:w-full"
|
||||
:class="getGroupColorClass(group.color)"
|
||||
></div>
|
||||
</a-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<UmsDirectChannelMerchantBasicInfo
|
||||
ref="basicInfoRef"
|
||||
:channel-mch-no="channelMchNo"
|
||||
:channel-merchant="channelMerchant"
|
||||
/>
|
||||
|
||||
<UmsDirectKeyConfigEdit ref="keyConfigRef" :channel-mch-no="channelMchNo" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 220px);
|
||||
gap: 24px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.isv-card {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.line-clamp-1 {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,7 @@
|
||||
import AlipayChannelMerchantManage from '#/views/payment/channel/alipay/manage/mch/AlipayChannelMerchantManage.vue';
|
||||
import AlipayMchManage from '#/views/payment/channel/alipay/manage/mch/AlipayMchManage.vue';
|
||||
import DouyinDirectMchManage from '#/views/payment/channel/douyin/manage/DouyinDirectMchManage.vue';
|
||||
import UmsDirectMchManage from '#/views/payment/channel/ums/manage/UmsDirectMchManage.vue';
|
||||
import WechatChannelMerchantManage from '#/views/payment/channel/wechat/manage/mch/WechatChannelMerchantManage.vue';
|
||||
import WechatDirectMchManage from '#/views/payment/channel/wechat/manage/mch/WechatDirectMchManage.vue';
|
||||
|
||||
@@ -49,6 +50,19 @@
|
||||
const wechatManageRef = ref<InstanceType<typeof WechatChannelMerchantManage>>();
|
||||
const wechatDirectManageRef = ref<InstanceType<typeof WechatDirectMchManage>>();
|
||||
const douyinDirectManageRef = ref<InstanceType<typeof DouyinDirectMchManage>>();
|
||||
const umsDirectManageRef = ref<InstanceType<typeof UmsDirectMchManage>>();
|
||||
|
||||
/** 是否为银联商务系列产品 */
|
||||
function isUmsProduct(p: string) {
|
||||
return (
|
||||
p === ProductEnum.UMS_QRCODE ||
|
||||
p === ProductEnum.UMS_JSAPI ||
|
||||
p === ProductEnum.UMS_APP ||
|
||||
p === ProductEnum.UMS_MINI ||
|
||||
p === ProductEnum.UMS_H5 ||
|
||||
p === ProductEnum.UMS_BARCODE
|
||||
);
|
||||
}
|
||||
|
||||
/** 是否为已支持的通道产品 */
|
||||
function isSupported(p: string) {
|
||||
@@ -57,7 +71,8 @@
|
||||
p === ProductEnum.ALIPAY_ISV ||
|
||||
p === ProductEnum.WECHAT_ISV ||
|
||||
p === ProductEnum.WECHAT_PAY ||
|
||||
p === ProductEnum.DOUYIN_PAY
|
||||
p === ProductEnum.DOUYIN_PAY ||
|
||||
isUmsProduct(p)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,6 +93,9 @@
|
||||
if (productCode === ProductEnum.DOUYIN_PAY) {
|
||||
return $t('payment.channel.douyinManage.manageTitle');
|
||||
}
|
||||
if (isUmsProduct(productCode)) {
|
||||
return $t('payment.channel.umsManage.manageTitle');
|
||||
}
|
||||
return $t('payment.merchant.channelMerchant.manageTitleDefault');
|
||||
}
|
||||
|
||||
@@ -129,6 +147,9 @@
|
||||
if (product.value === ProductEnum.DOUYIN_PAY) {
|
||||
douyinDirectManageRef.value?.init(mchNo.value, channelMchNo, channelMerchant.value);
|
||||
}
|
||||
if (isUmsProduct(product.value)) {
|
||||
umsDirectManageRef.value?.init(mchNo.value, channelMchNo, channelMerchant.value);
|
||||
}
|
||||
}
|
||||
|
||||
/** 从路由同步 query 至本地状态 */
|
||||
@@ -212,6 +233,7 @@
|
||||
<WechatChannelMerchantManage v-else-if="product === ProductEnum.WECHAT_ISV" ref="wechatManageRef" />
|
||||
<WechatDirectMchManage v-else-if="product === ProductEnum.WECHAT_PAY" ref="wechatDirectManageRef" />
|
||||
<DouyinDirectMchManage v-else-if="product === ProductEnum.DOUYIN_PAY" ref="douyinDirectManageRef" />
|
||||
<UmsDirectMchManage v-else-if="isUmsProduct(product)" ref="umsDirectManageRef" />
|
||||
<div v-else-if="!isSupported(product)" class="flex items-center justify-center" style="min-height: 400px">
|
||||
<a-empty :description="$t('payment.merchant.channelMerchant.detailNotSupportYet')" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user