feat(alipay): 应用授权令牌设置弹窗+原代运营授权卡片更名

This commit is contained in:
DaxPay Dev
2026-07-05 00:03:32 +08:00
parent 4021b7b0a4
commit 75ac1c4b5c
5 changed files with 235 additions and 75 deletions

View File

@@ -12,6 +12,12 @@ export const AlipayIsvChannelMerchantApi = {
create(data: AlipayIsvChannelMerchantCreateParam): Promise<Result<void>> {
return defHttp.post({ url: '/admin/alipay/isv-channel-merchant/create', data });
},
/**
* 更新应用授权令牌(手动设置/更新, 适用于令牌补充或过期/变更后重新绑定)
*/
updateAppAuthToken(data: AlipayIsvAppAuthTokenUpdateParam): Promise<Result<void>> {
return defHttp.post({ url: '/admin/alipay/isv-channel-merchant/update-app-auth-token', data });
},
/**
* 根据通道商户号查询支付宝服务商通道商户配置
*/
@@ -103,3 +109,13 @@ export interface AlipayDirectChannelMerchantCreateParam {
/** 支付宝商家用户ID(2088开头) */
alipayUserId: string;
}
/**
* 应用授权令牌更新参数
*/
export interface AlipayIsvAppAuthTokenUpdateParam {
/** 通道商户号 */
channelMchNo: string;
/** 应用授权令牌 */
appAuthToken: string;
}

View File

@@ -18,8 +18,8 @@
"groupApp": "App Management",
"cardBasicInfo": "Basic Info",
"cardBasicInfoDesc": "View channel merchant basic configuration",
"cardAuthOperation": "Auth Operation",
"cardAuthOperationDesc": "Authorize platform to operate Alipay merchant, get app auth token",
"cardAuthOperation": "App Auth Token",
"cardAuthOperationDesc": "Set or update the app auth token for platform-operated Alipay merchant",
"cardApp": "App Management",
"cardAppDesc": "Channel merchant app creation and management",
"basicInfoDrawerTitle": "Basic Info",
@@ -75,6 +75,12 @@
"appAuthTokenPlaceholder": "Please enter app auth token",
"appAuthTokenHelp": "The app_auth_token value obtained after merchant authorization",
"appAuthTokenRequired": "Please enter app auth token",
"appAuthTokenSetTitle": "Set App Auth Token",
"appAuthTokenCurrent": "Current Token",
"appAuthTokenNew": "New Token",
"appAuthTokenNewPlaceholder": "Please enter the new app auth token",
"appAuthTokenUpdateConfirm": "Confirm updating the app auth token?",
"appAuthTokenUpdateSuccess": "App auth token updated successfully",
"alipayIsvApp": "Alipay Application",
"alipayIsvAppPlaceholder": "Please select an Alipay application",
"alipayIsvAppRequired": "Please select an Alipay application",

View File

@@ -18,8 +18,8 @@
"groupApp": "应用管理",
"cardBasicInfo": "基本信息",
"cardBasicInfoDesc": "查看通道商户基础配置信息",
"cardAuthOperation": "代运营授权",
"cardAuthOperationDesc": "授权平台代运营支付宝商户,获取应用授权令牌",
"cardAuthOperation": "应用授权令牌",
"cardAuthOperationDesc": "设置或更新应用授权令牌(授权密钥),用于平台代运营支付宝商户",
"cardApp": "应用管理",
"cardAppDesc": "通道商户应用创建与管理",
"basicInfoDrawerTitle": "基本信息",
@@ -75,6 +75,12 @@
"appAuthTokenPlaceholder": "请输入应用授权令牌",
"appAuthTokenHelp": "商家授权后获取到的app_auth_token值",
"appAuthTokenRequired": "请输入应用授权令牌",
"appAuthTokenSetTitle": "设置应用授权令牌",
"appAuthTokenCurrent": "当前令牌",
"appAuthTokenNew": "新令牌",
"appAuthTokenNewPlaceholder": "请输入新的应用授权令牌",
"appAuthTokenUpdateConfirm": "确认更新应用授权令牌?",
"appAuthTokenUpdateSuccess": "应用授权令牌更新成功",
"alipayIsvApp": "支付宝应用",
"alipayIsvAppPlaceholder": "请选择支付宝应用",
"alipayIsvAppRequired": "请选择支付宝应用",

View File

@@ -0,0 +1,128 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { $t } from '@vben/locales';
import {
AlipayIsvChannelMerchantApi,
type AlipayIsvChannelMerchantConfig,
} from '#/api/payment/channel/alipay/channel-merchant.api';
import { useMessage } from '#/hooks/useMessage';
defineOptions({ name: 'AlipayAppAuthTokenUpdate' });
const emit = defineEmits<{
(e: 'success'): void;
}>();
const { confirm, message } = useMessage();
const visible = ref(false);
const loading = ref(false);
const saving = ref(false);
const channelMchNo = ref('');
// 服务商通道商户配置(用于展示当前令牌)
const isvConfig = ref<AlipayIsvChannelMerchantConfig>({});
// 新令牌输入
const newAuthToken = ref('');
/** 脱敏展示敏感字段 */
function maskSecret(value?: string) {
if (!value) {
return '-';
}
if (value.length <= 8) {
return '****';
}
return `${value.slice(0, 4)}****${value.slice(-4)}`;
}
/** 加载当前配置, 展示已有令牌 */
function loadConfig() {
if (!channelMchNo.value) {
return;
}
loading.value = true;
isvConfig.value = {};
AlipayIsvChannelMerchantApi.findByChannelMchNo(channelMchNo.value)
.then(({ data }) => {
isvConfig.value = data || {};
})
.finally(() => {
loading.value = false;
});
}
/** 打开弹窗 */
function open(mchNo: string) {
channelMchNo.value = mchNo;
newAuthToken.value = '';
visible.value = true;
loadConfig();
}
/** 关闭弹窗 */
function close() {
visible.value = false;
}
/** 保存新令牌(二次确认后提交) */
function handleSave() {
if (!newAuthToken.value.trim()) {
message.warning($t('payment.merchant.channelMerchant.appAuthTokenRequired'));
return;
}
confirm({
title: $t('common.confirm'),
content: $t('payment.merchant.channelMerchant.appAuthTokenUpdateConfirm'),
okText: $t('common.okText'),
cancelText: $t('common.cancelText'),
onOk() {
saving.value = true;
return AlipayIsvChannelMerchantApi.updateAppAuthToken({
channelMchNo: channelMchNo.value,
appAuthToken: newAuthToken.value.trim(),
})
.then(() => {
message.success($t('payment.merchant.channelMerchant.appAuthTokenUpdateSuccess'));
emit('success');
close();
})
.finally(() => {
saving.value = false;
});
},
});
}
defineExpose({ open, close });
</script>
<template>
<a-modal
v-model:open="visible"
:title="$t('payment.merchant.channelMerchant.appAuthTokenSetTitle')"
:confirm-loading="saving"
:ok-text="$t('common.save')"
:cancel-text="$t('common.cancelText')"
destroy-on-hidden
@ok="handleSave"
>
<a-spin :spinning="loading">
<a-form :label-col="{ span: 6 }" :wrapper-col="{ span: 16 }">
<!-- 国际化当前令牌 -->
<a-form-item :label="$t('payment.merchant.channelMerchant.appAuthTokenCurrent')">
{{ maskSecret(isvConfig.appAuthToken) }}
</a-form-item>
<!-- 国际化新令牌 -->
<a-form-item :label="$t('payment.merchant.channelMerchant.appAuthTokenNew')">
<a-input
v-model:value="newAuthToken"
:placeholder="$t('payment.merchant.channelMerchant.appAuthTokenNewPlaceholder')"
allow-clear
/>
</a-form-item>
</a-form>
</a-spin>
</a-modal>
</template>

View File

@@ -1,29 +1,30 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
import type { ChannelMerchantResult } from '#/api/payment/channel/channel-merchant.api';
import { $t } from '@vben/locales';
import { computed, ref } from 'vue';
import { IconifyIcon } from '@vben-core/icons';
import { $t } from '@vben/locales';
import type { ChannelMerchantResult } from '#/api/payment/channel/channel-merchant.api';
import { useMessage } from '#/hooks/useMessage';
import { IconifyIcon } from '@vben-core/icons';
import AlipayChannelMerchantBasicInfo from './AlipayChannelMerchantBasicInfo.vue';
import AlipayAppAuthTokenUpdate from './AlipayAppAuthTokenUpdate.vue';
import AlipayChannelMerchantBasicInfo from './AlipayChannelMerchantBasicInfo.vue';
defineOptions({ name: 'AlipayChannelMerchantManage' });
defineOptions({ name: 'AlipayChannelMerchantManage' });
const mchNo = ref('');
const channelMchNo = ref('');
const channelMerchant = ref<ChannelMerchantResult>({});
const basicInfoRef = ref<InstanceType<typeof AlipayChannelMerchantBasicInfo>>();
const { message } = useMessage();
const mchNo = ref('');
const channelMchNo = ref('');
const channelMerchant = ref<ChannelMerchantResult>({});
const basicInfoRef = ref<InstanceType<typeof AlipayChannelMerchantBasicInfo>>();
// 应用授权令牌更新弹窗
const authTokenUpdateRef = ref<InstanceType<typeof AlipayAppAuthTokenUpdate>>();
/** 功能卡片配置(服务商通道商户:仅基本信息) */
const functionCards = computed(() => [
{
// 国际化:基础管理
group: $t('payment.merchant.channelMerchant.groupBasic'),
color: 'blue',
/** 功能卡片配置(服务商通道商户:仅基本信息) */
const functionCards = computed(() => [
{
// 国际化:基础管理
group: $t('payment.merchant.channelMerchant.groupBasic'),
color: 'blue',
cards: [
{
key: 'basicInfo',
@@ -34,51 +35,50 @@ const functionCards = computed(() => [
},
{
key: 'authOperation',
// 国际化:代运营授权
// 国际化:应用授权令牌
title: $t('payment.merchant.channelMerchant.cardAuthOperation'),
icon: 'ant-design:safety-certificate-outlined',
description: $t('payment.merchant.channelMerchant.cardAuthOperationDesc'),
},
],
},
]);
},
]);
function getGroupColorClass(color: string) {
const map: Record<string, string> = {
blue: 'bg-blue-500',
green: 'bg-emerald-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',
};
return map[color] || 'bg-muted text-muted-foreground';
}
/** 初始化(由中转页调用) */
function init(no: string, mchChannelNo: string, summary: ChannelMerchantResult) {
mchNo.value = no;
channelMchNo.value = mchChannelNo;
channelMerchant.value = summary;
}
function handleCardClick(card: { key: string }) {
if (card.key === 'basicInfo') {
basicInfoRef.value?.open();
return;
function getGroupColorClass(color: string) {
const map: Record<string, string> = {
blue: 'bg-blue-500',
green: 'bg-emerald-500',
};
return map[color] || 'bg-gray-500';
}
if (card.key === 'authOperation') {
// 国际化:功能开发中,敬请期待
message.info($t('payment.merchant.channelMerchant.developing'));
return;
}
}
defineExpose({ init });
function getIconBgClass(color: string) {
const map: Record<string, string> = {
blue: 'bg-primary/10 text-primary',
green: 'bg-success/10 text-success',
};
return map[color] || 'bg-muted text-muted-foreground';
}
/** 初始化(由中转页调用) */
function init(no: string, mchChannelNo: string, summary: ChannelMerchantResult) {
mchNo.value = no;
channelMchNo.value = mchChannelNo;
channelMerchant.value = summary;
}
function handleCardClick(card: { key: string }) {
if (card.key === 'basicInfo') {
basicInfoRef.value?.open();
return;
}
if (card.key === 'authOperation') {
authTokenUpdateRef.value?.open(channelMchNo.value);
return;
}
}
defineExpose({ init });
</script>
<template>
@@ -104,7 +104,9 @@ defineExpose({ init });
>
<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">
<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">
@@ -126,25 +128,27 @@ defineExpose({ init });
:channel-mch-no="channelMchNo"
:channel-merchant="channelMerchant"
/>
<AlipayAppAuthTokenUpdate ref="authTokenUpdateRef" />
</div>
</template>
<style scoped>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, 220px);
gap: 24px;
justify-content: center;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, 220px);
gap: 24px;
justify-content: center;
}
.isv-card {
max-height: 200px;
}
.isv-card {
max-height: 200px;
}
.line-clamp-1 {
display: -webkit-box;
overflow: hidden;
-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>