refactor(payment): 通道密钥环境跟随支付产品生效环境(只读)

新增 resolveProductSandbox 工具统一从 pay_md_product_config.activeEnv 读取生效环境, adapay/alipay/ums/yeepay 四家直连密钥配置页把环境 radio 改为只读 tag, 禁止在密钥页切换环境; UmsDirectMchManage 透传 product 参数; payment/common 补 envFollowProductHint 六语种; 顺带 prettier 格式化 gateway-order.api.ts
This commit is contained in:
DaxPay Dev
2026-07-20 10:19:42 +08:00
parent 5a94b5f29b
commit 8235581bf1
13 changed files with 153 additions and 77 deletions

View File

@@ -9,7 +9,9 @@ export const GatewayOrderApi = {
/**
* 分页查询网关业务订单
*/
page(params: GatewayOrderQuery & { current?: number; size?: number }): Promise<Result<PageResult<GatewayOrderResult>>> {
page(
params: GatewayOrderQuery & { current?: number; size?: number },
): Promise<Result<PageResult<GatewayOrderResult>>> {
return defHttp.get({ url: '/admin/order/gateway-pay/page', params });
},

View File

@@ -0,0 +1,3 @@
{
"envFollowProductHint": "Follows the payment product setting. Switch the active environment in Product Configuration."
}

View File

@@ -0,0 +1,3 @@
{
"envFollowProductHint": "決済製品設定に従います。有効環境は「決済製品設定」で切り替えてください"
}

View File

@@ -0,0 +1,3 @@
{
"envFollowProductHint": "결제 상품 설정을 따릅니다. 유효 환경은 「결제 상품 설정」에서 전환하세요"
}

View File

@@ -0,0 +1,3 @@
{
"envFollowProductHint": "跟随支付产品配置,请在「支付产品配置」中切换生效环境"
}

View File

@@ -0,0 +1,3 @@
{
"envFollowProductHint": "跟隨支付產品配置,請在「支付產品配置」中切換生效環境"
}

View File

@@ -0,0 +1,3 @@
{
"envFollowProductHint": "跟隨支付產品配置,請在「支付產品配置」中切換生效環境"
}

View File

@@ -0,0 +1,15 @@
import { PayProductConfigApi } from '#/api/payment/config/pay-product-config.api';
/**
* 读取支付产品当前生效环境是否为沙箱
* 生效环境唯一真相源: pay_md_product_config.activeEnv
* 商户/密钥配置层禁止自行切换环境, 只能跟随产品
*/
export async function resolveProductSandbox(product: string): Promise<boolean> {
if (!product) {
return false;
}
const { data } = await PayProductConfigApi.listAll();
const cfg = (data || []).find((item) => item.product === product);
return cfg?.activeEnv === 'sandbox';
}

View File

@@ -5,9 +5,11 @@
import { AdapayDirectChannelMerchantApi, type AdapayDirectKeyConfig } from '#/api/payment/channel/adapay/channel-merchant.api';
import { PermCodes } from '#/constants/perm-codes';
import { ProductEnum } from '#/enums/payment/productEnum';
import { useFormEdit } from '#/hooks/useFormEdit';
import { useMessage } from '#/hooks/useMessage';
import { usePermission } from '#/hooks/usePermission';
import { resolveProductSandbox } from '#/utils/pay-product-env';
defineOptions({ name: 'AdapayDirectKeyConfigEdit' });
@@ -28,7 +30,7 @@
const form = ref<AdapayDirectKeyConfig>({} as AdapayDirectKeyConfig);
let rawForm: Record<string, any> = {};
// 是否沙箱环境
// 跟随支付产品生效环境(只读, 禁止在密钥页切换)
const sandbox = ref(false);
const canEdit = computed(() => hasPermission(PermCodes.Channel.Merchant.MANAGE));
@@ -41,26 +43,27 @@
privateKey: [{ required: true, message: $t('payment.channel.adapay.validation.privateKey') }],
};
/** 打开抽屉并加载密钥配置 */
function init() {
/** 打开抽屉并加载密钥配置(自动跟随产品生效环境) */
async function init() {
visible.value = true;
// 默认加载生产环境配置
sandbox.value = false;
resetForm();
loadConfig();
await loadConfig();
}
function loadConfig() {
async function loadConfig() {
if (!props.channelMchNo) return;
confirmLoading.value = true;
AdapayDirectChannelMerchantApi.findKeyConfig(props.channelMchNo, sandbox.value)
.then(({ data }) => {
rawForm = { ...data };
form.value = { ...data } as AdapayDirectKeyConfig;
})
.finally(() => {
confirmLoading.value = false;
});
try {
sandbox.value = await resolveProductSandbox(ProductEnum.ADA_PAY);
const { data } = await AdapayDirectChannelMerchantApi.findKeyConfig(
props.channelMchNo,
sandbox.value,
);
rawForm = { ...data };
form.value = { ...data } as AdapayDirectKeyConfig;
} finally {
confirmLoading.value = false;
}
}
function handleOk() {
@@ -116,12 +119,18 @@
>
<a-divider orientation="left">{{ $t('payment.channel.adapayManage.keyConfigSection') }}</a-divider>
<!-- 国际化: 环境(生产/沙箱切换) -->
<!-- 国际化: 跟随支付产品生效环境(只读) -->
<a-form-item :label="$t('payment.channel.adapayManage.environment')">
<a-radio-group v-model:value="sandbox" button-style="solid" @change="loadConfig">
<a-radio-button :value="false">{{ $t('payment.channel.adapayManage.prodEnv') }}</a-radio-button>
<a-radio-button :value="true">{{ $t('payment.channel.adapayManage.sandboxEnv') }}</a-radio-button>
</a-radio-group>
<a-tag :color="sandbox ? 'orange' : 'blue'">
{{
sandbox
? $t('payment.channel.adapayManage.sandboxEnv')
: $t('payment.channel.adapayManage.prodEnv')
}}
</a-tag>
<span class="ml-2 text-xs text-muted-foreground">
{{ $t('payment.common.envFollowProductHint') }}
</span>
</a-form-item>
<!-- 国际化: Adapay 商户号(创建时录入, 不可修改) -->

View File

@@ -7,11 +7,13 @@
import { AlipayMchAppApi, type AlipayMchAppKeyConfig } from '#/api/payment/channel/alipay/mch-app.api';
import { PermCodes } from '#/constants/perm-codes';
import { ProductEnum } from '#/enums/payment/productEnum';
import { useDict } from '#/hooks/useDict';
import { useFormEdit } from '#/hooks/useFormEdit';
import { useMessage } from '#/hooks/useMessage';
import { usePermission } from '#/hooks/usePermission';
import { readFileAsText } from '#/utils/file';
import { resolveProductSandbox } from '#/utils/pay-product-env';
const props = defineProps<{
aliAppId?: string;
@@ -28,7 +30,7 @@
const loading = ref(false);
const saving = ref(false);
const isEditing = ref(false);
// 是否沙箱环境
// 跟随支付产品生效环境(只读, 禁止在密钥页切换)
const sandbox = ref(false);
const formRef = ref();
const formState = ref<AlipayMchAppKeyConfig>({
@@ -67,26 +69,30 @@
],
}));
/** 加载密钥配置 */
function loadConfig() {
/** 加载密钥配置(自动跟随支付宝直连产品生效环境) */
async function loadConfig() {
if (!props.alipayDirectAppId) {
return;
}
loading.value = true;
AlipayMchAppApi.findKeyConfigByAlipayDirectAppId(props.alipayDirectAppId, sandbox.value)
.then(({ data }) => {
formState.value = {
authType: 'public_key',
...data,
alipayDirectAppId: props.alipayDirectAppId,
mchNo: props.mchNo,
channelMchNo: props.channelMchNo,
};
originalForm.value = { ...formState.value };
})
.finally(() => {
loading.value = false;
});
try {
// 环境由支付产品配置决定, 商户密钥页不可切换
sandbox.value = await resolveProductSandbox(ProductEnum.ALIPAY);
const { data } = await AlipayMchAppApi.findKeyConfigByAlipayDirectAppId(
props.alipayDirectAppId,
sandbox.value,
);
formState.value = {
authType: 'public_key',
...data,
alipayDirectAppId: props.alipayDirectAppId,
mchNo: props.mchNo,
channelMchNo: props.channelMchNo,
};
originalForm.value = { ...formState.value };
} finally {
loading.value = false;
}
}
function handleEdit() {
@@ -177,8 +183,6 @@
() => props.alipayDirectAppId,
(alipayDirectAppId) => {
if (alipayDirectAppId) {
// 切换应用默认加载生产环境配置
sandbox.value = false;
isEditing.value = false;
loadConfig();
}
@@ -221,12 +225,18 @@
>
<a-divider orientation="left">{{ $t('payment.channel.alipayIsv.basicConfig') }}</a-divider>
<!-- 国际化: 环境(生产/沙箱切换) -->
<!-- 国际化: 跟随支付产品生效环境(只读, 不可在此切换) -->
<a-form-item :label="$t('payment.channel.alipayIsv.environment')">
<a-radio-group v-model:value="sandbox" button-style="solid" @change="loadConfig">
<a-radio-button :value="false">{{ $t('payment.channel.alipayIsv.prodEnv') }}</a-radio-button>
<a-radio-button :value="true">{{ $t('payment.channel.alipayIsv.sandboxEnv') }}</a-radio-button>
</a-radio-group>
<a-tag :color="sandbox ? 'orange' : 'blue'">
{{
sandbox
? $t('payment.channel.alipayIsv.sandboxEnv')
: $t('payment.channel.alipayIsv.prodEnv')
}}
</a-tag>
<span class="ml-2 text-xs text-muted-foreground">
{{ $t('payment.common.envFollowProductHint') }}
</span>
</a-form-item>
<a-form-item :label="$t('payment.channel.alipayIsv.aliAppId')">

View File

@@ -8,11 +8,14 @@
import { useFormEdit } from '#/hooks/useFormEdit';
import { useMessage } from '#/hooks/useMessage';
import { usePermission } from '#/hooks/usePermission';
import { resolveProductSandbox } from '#/utils/pay-product-env';
defineOptions({ name: 'UmsDirectKeyConfigEdit' });
const props = defineProps<{
channelMchNo: string;
/** 支付产品编码(银联商务多产品, 用于读取生效环境) */
product?: string;
}>();
const emit = defineEmits<{
@@ -28,7 +31,7 @@
const form = ref<UmsDirectKeyConfig>({} as UmsDirectKeyConfig);
let rawForm: Record<string, any> = {};
// 是否沙箱环境
// 跟随支付产品生效环境(只读, 禁止在密钥页切换)
const sandbox = ref(false);
const canEdit = computed(() => hasPermission(PermCodes.Channel.Merchant.MANAGE));
@@ -42,26 +45,29 @@
secretKey: [{ required: true, message: $t('payment.channel.ums.validation.secretKey') }],
};
/** 打开抽屉并加载密钥配置 */
function init() {
/** 打开抽屉并加载密钥配置(自动跟随产品生效环境) */
async function init() {
visible.value = true;
// 默认加载生产环境配置
sandbox.value = false;
resetForm();
loadConfig();
await loadConfig();
}
function loadConfig() {
async function loadConfig() {
if (!props.channelMchNo) return;
confirmLoading.value = true;
UmsDirectChannelMerchantApi.findKeyConfig(props.channelMchNo, sandbox.value)
.then(({ data }) => {
rawForm = { ...data };
form.value = { ...data } as UmsDirectKeyConfig;
})
.finally(() => {
confirmLoading.value = false;
});
try {
// 银联商务多产品, 优先用通道商户所属 product
const product = props.product || 'ums_qrcode';
sandbox.value = await resolveProductSandbox(product);
const { data } = await UmsDirectChannelMerchantApi.findKeyConfig(
props.channelMchNo,
sandbox.value,
);
rawForm = { ...data };
form.value = { ...data } as UmsDirectKeyConfig;
} finally {
confirmLoading.value = false;
}
}
function handleOk() {
@@ -117,12 +123,18 @@
>
<a-divider orientation="left">{{ $t('payment.channel.umsManage.keyConfigSection') }}</a-divider>
<!-- 国际化: 环境(生产/沙箱切换) -->
<!-- 国际化: 跟随支付产品生效环境(只读) -->
<a-form-item :label="$t('payment.channel.umsManage.environment')">
<a-radio-group v-model:value="sandbox" button-style="solid" @change="loadConfig">
<a-radio-button :value="false">{{ $t('payment.channel.umsManage.prodEnv') }}</a-radio-button>
<a-radio-button :value="true">{{ $t('payment.channel.umsManage.sandboxEnv') }}</a-radio-button>
</a-radio-group>
<a-tag :color="sandbox ? 'orange' : 'blue'">
{{
sandbox
? $t('payment.channel.umsManage.sandboxEnv')
: $t('payment.channel.umsManage.prodEnv')
}}
</a-tag>
<span class="ml-2 text-xs text-muted-foreground">
{{ $t('payment.common.envFollowProductHint') }}
</span>
</a-form-item>
<!-- 国际化: 银联商务商户号(mid, 创建时录入, 不可修改) -->

View File

@@ -153,7 +153,11 @@
<ChannelMerchantNameEditModal ref="editNameRef" :channel-merchant="channelMerchant" @success="emit('success')" />
<UmsDirectKeyConfigEdit ref="keyConfigRef" :channel-mch-no="channelMchNo" />
<UmsDirectKeyConfigEdit
ref="keyConfigRef"
:channel-mch-no="channelMchNo"
:product="channelMerchant.product"
/>
</div>
</template>

View File

@@ -8,6 +8,7 @@ import {
type YeepayDirectKeyConfigParam,
} from '#/api/payment/channel/yeepay/channel-merchant.api';
import { useMessage } from '#/hooks/useMessage';
import { resolveProductSandbox } from '#/utils/pay-product-env';
defineOptions({ name: 'YeepayKeyConfigEdit' });
@@ -19,7 +20,9 @@ const formRef = ref();
// 当前编辑的通道商户号
const channelMchNo = ref('');
// 是否沙箱环境
// 所属支付产品(用于读取生效环境)
const product = ref('');
// 跟随支付产品生效环境(只读, 禁止在密钥页切换)
const sandbox = ref(false);
// 已配置标志(回显用)
const configured = ref({
@@ -55,11 +58,12 @@ const rules = {
],
};
/** 由通用框架初始化(传入通道商户号后加载密钥配置并打开弹窗) */
async function init(mchNo: string) {
/** 由通用框架初始化(传入通道商户号与产品编码后加载密钥配置并打开弹窗) */
async function init(mchNo: string, productCode?: string) {
channelMchNo.value = mchNo;
// 默认加载生产环境配置
sandbox.value = false;
product.value = productCode || 'yee_pay';
// 跟随支付产品生效环境
sandbox.value = await resolveProductSandbox(product.value);
visible.value = true;
form.value = {
channelMchNo: mchNo,
@@ -73,7 +77,7 @@ async function init(mchNo: string) {
await loadConfig();
}
/** 加载当前环境(生产/沙箱)的密钥配置(脱敏返回) */
/** 加载当前产品生效环境对应的密钥配置(脱敏返回) */
async function loadConfig() {
if (!channelMchNo.value) return;
const { data } = await YeepayChannelMerchantApi.findKeyConfig(channelMchNo.value, sandbox.value);
@@ -113,12 +117,14 @@ defineExpose({ init });
@ok="handleSubmit"
>
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<!-- 国际化: 环境(生产/沙箱切换) -->
<!-- 国际化: 跟随支付产品生效环境(只读) -->
<a-form-item :label="$t('payment.channel.yeepay.environment')">
<a-radio-group v-model:value="sandbox" button-style="solid" @change="loadConfig">
<a-radio-button :value="false">{{ $t('payment.channel.yeepay.prodEnv') }}</a-radio-button>
<a-radio-button :value="true">{{ $t('payment.channel.yeepay.sandboxEnv') }}</a-radio-button>
</a-radio-group>
<a-tag :color="sandbox ? 'orange' : 'blue'">
{{ sandbox ? $t('payment.channel.yeepay.sandboxEnv') : $t('payment.channel.yeepay.prodEnv') }}
</a-tag>
<span class="ml-2 text-xs text-muted-foreground">
{{ $t('payment.common.envFollowProductHint') }}
</span>
</a-form-item>
<a-form-item :label="$t('payment.channel.yeepay.appKey')" name="appKey">
<a-textarea