feat: 支付宝通道商户应用管理(ISV/直连)拆分重构

This commit is contained in:
DaxPay Dev
2026-06-14 13:01:12 +08:00
parent 17ff7a35e6
commit 7d6963ed23
8 changed files with 171 additions and 102 deletions

View File

@@ -0,0 +1,105 @@
import type { MchEntity, Result } from '#/types/web';
import { defHttp } from '#/api/request';
/**
* 支付宝服务商通道商户 API
*/
export const AlipayIsvChannelMerchantApi = {
/**
* 创建支付宝服务商通道商户
*/
create(data: AlipayIsvChannelMerchantCreateParam): Promise<Result<void>> {
return defHttp.post({ url: '/admin/alipay/isv-channel-merchant/create', data });
},
/**
* 根据通道商户号查询支付宝服务商通道商户配置
*/
findByChannelMchNo(channelMchNo: string): Promise<Result<AlipayIsvChannelMerchantConfig>> {
return defHttp.get({
url: '/admin/alipay/isv-channel-merchant/find-by-channel-mch-no',
params: { channelMchNo },
});
},
};
/**
* 支付宝直连通道商户 API
*/
export const AlipayDirectChannelMerchantApi = {
/**
* 创建支付宝直连通道商户
*/
create(data: AlipayDirectChannelMerchantCreateParam): Promise<Result<void>> {
return defHttp.post({ url: '/admin/alipay/direct-channel-merchant/create', data });
},
/**
* 根据通道商户号查询支付宝直连通道商户配置
*/
findByChannelMchNo(channelMchNo: string): Promise<Result<AlipayDirectChannelMerchantConfig>> {
return defHttp.get({
url: '/admin/alipay/direct-channel-merchant/find-by-channel-mch-no',
params: { channelMchNo },
});
},
};
/**
* 支付宝服务商通道商户配置
*/
export interface AlipayIsvChannelMerchantConfig extends MchEntity {
/** 通道商户号 */
channelMchNo?: string;
/** 所属支付产品 */
product?: string;
/** 关联服务商应用ID(系统主键, 指向 alipay_isv_app.id) */
isvAppId?: string;
/** 子商户支付宝识别码(2088开头) */
alipayUserId?: string;
/** 应用授权令牌(会过期/刷新) */
appAuthToken?: string;
}
/**
* 支付宝直连通道商户配置
*/
export interface AlipayDirectChannelMerchantConfig extends MchEntity {
/** 通道商户号(系统生成雪花号) */
channelMchNo?: string;
/** 所属支付产品 */
product?: string;
/** 支付宝商家唯一识别码(2088开头) */
alipayUserId?: string;
}
/**
* 支付宝服务商通道商户创建参数
*/
export interface AlipayIsvChannelMerchantCreateParam {
/** 商户号 */
mchNo: string;
/** 通道商户名称 */
channelMerchantName: string;
/** 所属支付产品 */
product: string;
/** 服务商应用ID(系统主键) */
appId: string;
/** 子商户支付宝用户ID(2088开头) */
alipayUserId: string;
/** 应用授权令牌 */
appAuthToken: string;
}
/**
* 支付宝直连通道商户创建参数
*/
export interface AlipayDirectChannelMerchantCreateParam {
/** 商户号 */
mchNo: string;
/** 通道商户名称 */
channelMerchantName: string;
/** 所属支付产品 */
product: string;
/** 支付宝商家用户ID(2088开头) */
alipayUserId: string;
}

View File

@@ -108,33 +108,6 @@ export interface ChannelMerchantBaseCreateParam {
channel: string;
}
/**
* 支付宝通道商户创建 API
*/
export const ChannelMerchantAlipayApi = {
/**
* 创建支付宝通道商户(服务商)
*/
create(data: Record<string, any>): Promise<Result<void>> {
return defHttp.post({ url: '/admin/alipay/channel-merchant/create', data });
},
/**
* 创建支付宝通道商户(直连)
*/
createDirect(data: Record<string, any>): Promise<Result<string>> {
return defHttp.post({ url: '/admin/alipay/channel-merchant/create-direct', data });
},
/**
* 根据通道商户号查询支付宝通道商户配置
*/
findByChannelMchNo(channelMchNo: string): Promise<Result<AlipayChannelMerchantConfig>> {
return defHttp.get({
url: '/admin/alipay/channel-merchant/find-by-channel-mch-no',
params: { channelMchNo },
});
},
};
/**
* 微信通道商户创建 API
*/
@@ -192,20 +165,6 @@ export const ChannelMerchantDouyinApi = {
},
};
/** 支付宝通道商户配置 */
export interface AlipayChannelMerchantConfig extends MchEntity {
/** 通道商户号 */
channelMchNo?: string;
/** 所属支付产品 */
product?: string;
/** 支付宝服务商应用 ID */
isvAppId?: string;
/** 子商户号 */
alipayUserId?: string;
/** 应用授权令牌 */
appAuthToken?: string;
}
/** 微信通道商户配置 */
export interface WechatChannelMerchantConfig extends MchEntity {
/** 通道商户号 */

View File

@@ -2,7 +2,5 @@
"title": "Alipay Direct Management",
"groupApp": "App Management",
"cardMchApp": "Alipay Channel Merchant Apps",
"cardMchAppDesc": "Manage Alipay channel merchant applications",
"cardAddMchApp": "Add Alipay App",
"cardAddMchAppDesc": "Create a new Alipay channel merchant app"
"cardMchAppDesc": "Manage Alipay channel merchant applications"
}

View File

@@ -2,7 +2,5 @@
"title": "支付宝直连管理",
"groupApp": "应用管理",
"cardMchApp": "支付宝通道商户应用",
"cardMchAppDesc": "管理支付宝通道商户应用",
"cardAddMchApp": "添加支付宝应用",
"cardAddMchAppDesc": "创建新的支付宝通道商户应用"
"cardMchAppDesc": "管理支付宝通道商户应用"
}

View File

@@ -5,7 +5,7 @@ import { $t } from '@vben/locales';
import { IconifyIcon } from '@vben-core/icons';
import { ChannelMerchantAlipayApi } from '#/api/payment/channelMerchant.api';
import { AlipayDirectChannelMerchantApi } from '#/api/payment/alipayChannelMerchant.api';
import ChannelLogo from '#/components/channel/ChannelLogo.vue';
import { channelI18nMap, channelNameMap, productI18nMap, productNameMap } from '#/enums/payment';
import { useMessage } from '#/hooks/useMessage';
@@ -74,10 +74,9 @@ function handlePrev() {
function handleSubmit() {
formRef.value?.validate().then(() => {
submitLoading.value = true;
ChannelMerchantAlipayApi.createDirect({
AlipayDirectChannelMerchantApi.create({
mchNo: mchNo.value,
product: productCode.value,
channel: 'alipay',
...form.value,
})
.then(() => {

View File

@@ -6,7 +6,10 @@ import { $t } from '@vben/locales';
import { IconifyIcon } from '@vben-core/icons';
import { type AlipayIsvApp, AlipayIsvAppApi } from '#/api/payment/alipayIsvApp.api';
import { ChannelMerchantAlipayApi } from '#/api/payment/channelMerchant.api';
import {
type AlipayIsvChannelMerchantCreateParam,
AlipayIsvChannelMerchantApi,
} from '#/api/payment/alipayChannelMerchant.api';
import ChannelLogo from '#/components/channel/ChannelLogo.vue';
import { channelI18nMap, channelNameMap, productI18nMap, productNameMap } from '#/enums/payment';
import { useMessage } from '#/hooks/useMessage';
@@ -26,7 +29,7 @@ const channelCode = ref('');
const formRef = ref();
const form = ref({
channelMerchantName: '',
isvAppId: undefined as string | undefined,
appId: undefined as string | undefined,
alipayUserId: '',
appAuthToken: '',
});
@@ -61,15 +64,15 @@ const productDisplayName = computed(() => {
const rules = computed(() => ({
channelMerchantName: [{ required: true, message: $t('payment.merchant.channelMerchant.channelMerchantNameRequired') }],
isvAppId: [{ required: true, message: $t('payment.merchant.channelMerchant.alipayIsvAppRequired') }],
appId: [{ required: true, message: $t('payment.merchant.channelMerchant.alipayIsvAppRequired') }],
alipayUserId: [{ required: true, message: $t('payment.merchant.channelMerchant.alipaySubMerchantNoRequired') }],
appAuthToken: [{ required: true, message: $t('payment.merchant.channelMerchant.appAuthTokenRequired') }],
}));
/** 支付宝应用下拉选项antdv-next Select 需使用 options 属性) */
/** 支付宝应用下拉选项(值为系统主键id, 展示应用名+支付宝APPID便于识别) */
const isvAppOptions = computed(() =>
isvAppList.value.map((item) => ({
value: item.aliAppId,
value: item.id,
label: `${item.appName} (${item.aliAppId})`,
})),
);
@@ -109,10 +112,8 @@ function getData(): Record<string, any> {
}
function submit(param: Record<string, any>): Promise<any> {
return ChannelMerchantAlipayApi.create({
...param,
...form.value,
});
// param 已由 handleSubmit 组装完整(mchNo/product/form 各字段), 直接透传
return AlipayIsvChannelMerchantApi.create(param as AlipayIsvChannelMerchantCreateParam);
}
function handlePrev() {
@@ -125,7 +126,6 @@ function handleSubmit() {
const param = {
mchNo: mchNo.value,
product: productCode.value,
channel: 'alipay',
...form.value,
};
submit(param)
@@ -142,7 +142,7 @@ function handleSubmit() {
function resetForm() {
form.value = {
channelMerchantName: '',
isvAppId: undefined,
appId: undefined,
alipayUserId: '',
appAuthToken: '',
};
@@ -180,7 +180,7 @@ defineExpose({ init, validate, getData, submit });
/>
</a-form-item>
<!-- 国际化支付宝应用 -->
<a-form-item name="isvAppId">
<a-form-item name="appId">
<template #label>
{{ $t('payment.merchant.channelMerchant.alipayIsvApp') }}
<a-tooltip :title="$t('payment.merchant.channelMerchant.alipayIsvAppHelp')">
@@ -188,7 +188,7 @@ defineExpose({ init, validate, getData, submit });
</a-tooltip>
</template>
<a-select
v-model:value="form.isvAppId"
v-model:value="form.appId"
:options="isvAppOptions"
:loading="isvAppLoading"
:placeholder="$t('payment.merchant.channelMerchant.alipayIsvAppPlaceholder')"

View File

@@ -3,11 +3,14 @@ import { computed, ref, watch } from 'vue';
import { $t } from '@vben/locales';
import { type AlipayIsvApp, AlipayIsvAppApi } from '#/api/payment/alipayIsvApp.api';
import {
ChannelMerchantAlipayApi,
type AlipayChannelMerchantConfig,
type ChannelMerchantResult,
} from '#/api/payment/channelMerchant.api';
AlipayDirectChannelMerchantApi,
AlipayIsvChannelMerchantApi,
type AlipayDirectChannelMerchantConfig,
type AlipayIsvChannelMerchantConfig,
} from '#/api/payment/alipayChannelMerchant.api';
import { type ChannelMerchantResult } from '#/api/payment/channelMerchant.api';
import { ProductEnum } from '#/enums/payment/productEnum';
defineOptions({ name: 'AlipayChannelMerchantBasicInfo' });
@@ -19,7 +22,12 @@ const props = defineProps<{
const visible = ref(false);
const loading = ref(false);
const config = ref<AlipayChannelMerchantConfig>({});
// 服务商通道商户配置(含 isvAppId / alipayUserId / appAuthToken)
const isvConfig = ref<AlipayIsvChannelMerchantConfig>({});
// 直连通道商户配置(含 alipayUserId)
const directConfig = ref<AlipayDirectChannelMerchantConfig>({});
// 关联的服务商应用详情(用于展示 aliAppId, 让用户能识别对应的应用)
const isvAppInfo = ref<AlipayIsvApp>({});
/** 是否为直连产品 */
const isDirectProduct = computed(() => props.channelMerchant.product === ProductEnum.ALIPAY);
@@ -54,15 +62,38 @@ const sourceLabel = computed(() => {
return props.channelMerchant.source || '-';
});
/** 加载支付宝通道商户配置 */
/**
* 加载支付宝通道商户配置
* 按支付产品类型分别调用服务商/直连接口
*/
function loadConfig() {
if (!props.channelMchNo) {
return;
}
loading.value = true;
ChannelMerchantAlipayApi.findByChannelMchNo(props.channelMchNo)
// 重置状态
isvConfig.value = {};
directConfig.value = {};
isvAppInfo.value = {};
const query = isIsvProduct.value
? AlipayIsvChannelMerchantApi.findByChannelMchNo(props.channelMchNo)
: AlipayDirectChannelMerchantApi.findByChannelMchNo(props.channelMchNo);
query
.then(({ data }) => {
config.value = data || {};
if (isIsvProduct.value) {
isvConfig.value = data || {};
// 服务商场景: 查应用详情, 展示 aliAppId 让用户能识别对应应用
const appId = isvConfig.value.isvAppId;
if (appId) {
return AlipayIsvAppApi.findById(appId).then(({ data: appData }) => {
isvAppInfo.value = appData || {};
});
}
} else {
directConfig.value = data || {};
}
})
.finally(() => {
loading.value = false;
@@ -100,7 +131,7 @@ defineExpose({ open, close });
<a-spin :spinning="loading">
<a-descriptions bordered :column="1" size="small">
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.channelMerchantNo')">
{{ channelMerchant.channelMchNo || config.channelMchNo || '-' }}
{{ channelMerchant.channelMchNo || '-' }}
</a-descriptions-item>
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.channelMerchantName')">
{{ channelMerchant.channelMerchantName || '-' }}
@@ -112,17 +143,17 @@ defineExpose({ open, close });
{{ sourceLabel }}
</a-descriptions-item>
<a-descriptions-item v-if="isDirectProduct" :label="$t('payment.channel.alipay.alipayUserId')">
{{ config.alipayUserId || '-' }}
{{ directConfig.alipayUserId || '-' }}
</a-descriptions-item>
<template v-if="isIsvProduct">
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.alipayIsvApp')">
{{ config.isvAppId || '-' }}
{{ isvAppInfo.aliAppId || '-' }}
</a-descriptions-item>
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.alipaySubMerchantNo')">
{{ config.alipayUserId || '-' }}
{{ isvConfig.alipayUserId || '-' }}
</a-descriptions-item>
<a-descriptions-item :label="$t('payment.merchant.channelMerchant.appAuthToken')">
{{ config.appAuthToken ? maskSecret(config.appAuthToken) : '-' }}
{{ isvConfig.appAuthToken ? maskSecret(isvConfig.appAuthToken) : '-' }}
</a-descriptions-item>
</template>
</a-descriptions>

View File

@@ -8,8 +8,9 @@
import type { ChannelMerchantResult } from '#/api/payment/channelMerchant.api';
import { ProductEnum } from '#/enums/payment/productEnum';
import AlipayChannelMerchantBasicInfo from './AlipayChannelMerchantBasicInfo.vue';
import AlipayMchAppEdit from './app/AlipayMchAppEdit.vue';
defineOptions({ name: 'AlipayMchManage' });
@@ -19,7 +20,6 @@
const channelMchNo = ref('');
const channelMerchant = ref<ChannelMerchantResult>({});
const basicInfoRef = ref<InstanceType<typeof AlipayChannelMerchantBasicInfo>>();
const mchAppEditRef = ref<InstanceType<typeof AlipayMchAppEdit>>();
/** 功能卡片配置 */
const functionCards = computed(() => [
@@ -42,14 +42,6 @@
group: $t('payment.channel.alipayMchManage.groupApp'),
color: 'blue',
cards: [
{
key: 'addMchApp',
// 国际化:添加支付宝应用
title: $t('payment.channel.alipayMchManage.cardAddMchApp'),
icon: 'ant-design:plus-circle-outlined',
// 国际化:创建新的支付宝通道商户应用
description: $t('payment.channel.alipayMchManage.cardAddMchAppDesc'),
},
{
key: 'mchApp',
// 国际化:通道商户应用
@@ -88,33 +80,21 @@
channelMerchant.value = summary;
}
/** 添加应用成功,跳转到应用管理页 */
function handleAddAppOk() {
router.push({
path: '/payment/merchant/channel-merchant/alipay-app-manage',
query: {
mchNo: mchNo.value,
channelMchNo: channelMchNo.value,
},
});
}
/** 卡片点击 */
function handleCardClick(card: { key: string; route?: string }) {
if (card.key === 'basicInfo') {
basicInfoRef.value?.open();
return;
}
if (card.key === 'addMchApp') {
mchAppEditRef.value?.show(mchNo.value, channelMchNo.value);
return;
}
if (card.route) {
router.push({
path: card.route,
query: {
mchNo: mchNo.value,
channelMchNo: channelMchNo.value,
// 国际化:透传通道商户 id 与产品类型,使应用管理页返回时能回到详情页
channelMerchantId: channelMerchant.value.id,
product: ProductEnum.ALIPAY,
},
});
}
@@ -176,7 +156,6 @@
:channel-mch-no="channelMchNo"
:channel-merchant="channelMerchant"
/>
<AlipayMchAppEdit ref="mchAppEditRef" @ok="handleAddAppOk" />
</div>
</template>