feat(admin): 码牌 DIRECT 能力候选与 openId 风控提示

This commit is contained in:
DaxPay Dev
2026-07-17 22:48:25 +08:00
parent 51f35ff153
commit 98f8caaa9e
24 changed files with 107 additions and 42 deletions

View File

@@ -22,6 +22,29 @@ export const CodeConfigApi = {
data,
});
},
/**
* DIRECT: 按商户+支付渠道列通道商户候选(不绑默认 JSAPI method
*/
listDirectChannelMchCandidates(params: {
mchNo: string
provider: string
}): Promise<Result<LabelValue[]>> {
return defHttp.get({
url: '/admin/gateway/code-config/direct-channel-mch-candidates',
params,
});
},
/**
* DIRECT: 按通道商户列全部已挂载支付能力(含 H5/主扫等)
*/
listDirectCapabilityCandidates(channelMchNo: string): Promise<Result<LabelValue[]>> {
return defHttp.get({
url: '/admin/gateway/code-config/direct-capability-candidates',
params: { channelMchNo },
});
},
};
/** 码牌支付策略配置结果 */

View File

@@ -40,6 +40,7 @@
"directModeHint": "Set channel merchant and capability only where needed; skip channel routing",
"atLeastOneRequired": "Configure at least one environment and form",
"partialRowIncomplete": "Channel merchant and capability must both be set",
"methodModeHint": "Set pay methods only for env/forms you need; channel merchant follows channel routing"
"methodModeHint": "Set pay methods only for env/forms you need; channel merchant follows channel routing",
"openIdRiskHint": "When using openId risk blacklist for pre-pay blocking, code-plate environments must use methods that can obtain a user identity (e.g. WeChat JSAPI/mini, Alipay JSAPI). H5 redirect / QR methods without openId cannot be blocked by openId before payment (IP blacklist still works)."
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "Type and value cannot be changed after creation",
"expireEmpty": "Leave empty for permanent"
"expireEmpty": "Leave empty for permanent",
"openIdCodePayHint": "openId blacklist only applies to trades that already have a user identity before pay (e.g. code-plate/aggregate JSAPI or mini). If code-plate uses a method without openId, this list cannot block the user before payment."
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "Atur pedagang saluran dan kemampuan H5/Mini hanya yang dibutuhkan; lewati perutean",
"atLeastOneRequired": "Konfigurasi minimal satu lingkungan dan bentuk",
"partialRowIncomplete": "Pedagang saluran dan kemampuan harus diisi bersama",
"methodModeHint": "Atur metode H5/Mini hanya yang dibutuhkan; pedagang saluran mengikuti perutean"
"methodModeHint": "Atur metode H5/Mini hanya yang dibutuhkan; pedagang saluran mengikuti perutean",
"openIdRiskHint": "When using openId risk blacklist for pre-pay blocking, code-plate environments must use methods that can obtain a user identity (e.g. WeChat JSAPI/mini, Alipay JSAPI). H5 redirect / QR methods without openId cannot be blocked by openId before payment (IP blacklist still works)."
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "Jenis dan nilai tidak dapat diubah setelah dibuat",
"expireEmpty": "Kosongkan untuk permanen"
"expireEmpty": "Kosongkan untuk permanen",
"openIdCodePayHint": "openId blacklist only applies to trades that already have a user identity before pay (e.g. code-plate/aggregate JSAPI or mini). If code-plate uses a method without openId, this list cannot block the user before payment."
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "必要な環境の H5/ミニだけチャネル加盟店と能力を指定(一部のみ可)。通道路由をスキップします",
"atLeastOneRequired": "少なくとも1つの環境×形態を設定してください",
"partialRowIncomplete": "チャネル加盟店と支払能力は両方必須です",
"methodModeHint": "必要な環境の H5/ミニだけ支払い方法を指定(一部のみ可)。チャネル加盟店は通道路由に従います"
"methodModeHint": "必要な環境の H5/ミニだけ支払い方法を指定(一部のみ可)。チャネル加盟店は通道路由に従います",
"openIdRiskHint": "openId リスクブラックリストで支払前遮断を行う場合、コードプレート側はユーザー識別子を取得できる決済方式WeChat JSAPI/ミニ、Alipay JSAPI 等を設定してください。openId 不要の H5/QR では支払前に openId で遮断できませんIP リストは有効)。"
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "Type and value cannot be changed after creation",
"expireEmpty": "Leave empty for permanent"
"expireEmpty": "Leave empty for permanent",
"openIdCodePayHint": "openId リストは支払前にユーザー識別子を取得済みの取引にのみ有効です(コードプレート/集約の JSAPI・ミニ等。openId 不要の方式では支払前に遮断できません。"
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "필요한 환경의 H5/미니만 채널 가맹점과 능력 지정(일부만 가능). 채널 라우팅을 건너뜁니다",
"atLeastOneRequired": "환경×형태를 하나 이상 설정하세요",
"partialRowIncomplete": "채널 가맹점과 결제 능력은 함께 입력해야 합니다",
"methodModeHint": "필요한 환경의 H5/미니만 결제 방식 지정(일부만 가능). 채널 가맹점은 채널 라우팅을 따릅니다"
"methodModeHint": "필요한 환경의 H5/미니만 결제 방식 지정(일부만 가능). 채널 가맹점은 채널 라우팅을 따릅니다",
"openIdRiskHint": "openId 리스크 블랙리스트로 결제 전 차단을 쓰려면 코드 플레이트 환경에 사용자 식별자를 받을 수 있는 결제 방식(위챗 JSAPI/미니, 알리페이 JSAPI 등)을 설정하세요. openId 없는 H5/QR 은 결제 전 openId 차단이 불가합니다(IP 목록은 유효)."
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "Type and value cannot be changed after creation",
"expireEmpty": "Leave empty for permanent"
"expireEmpty": "Leave empty for permanent",
"openIdCodePayHint": "openId 목록은 결제 전 사용자 식별자가 이미 있는 거래에만 적용됩니다(코드 플레이트/집계 JSAPI·미니 등). openId 없는 방식은 결제 전 차단이 불가합니다."
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "Tetapkan peniaga saluran dan keupayaan H5/mini hanya bila perlu; langkau perutean",
"atLeastOneRequired": "Konfigurasi sekurang-kurangnya satu persekitaran dan bentuk",
"partialRowIncomplete": "Peniaga saluran dan keupayaan mesti diisi bersama",
"methodModeHint": "Tetapkan kaedah H5/mini hanya yang diperlukan; peniaga saluran ikut perutean"
"methodModeHint": "Tetapkan kaedah H5/mini hanya yang diperlukan; peniaga saluran ikut perutean",
"openIdRiskHint": "When using openId risk blacklist for pre-pay blocking, code-plate environments must use methods that can obtain a user identity (e.g. WeChat JSAPI/mini, Alipay JSAPI). H5 redirect / QR methods without openId cannot be blocked by openId before payment (IP blacklist still works)."
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "Jenis dan nilai tidak boleh diubah selepas dicipta",
"expireEmpty": "Biarkan kosong untuk kekal"
"expireEmpty": "Biarkan kosong untuk kekal",
"openIdCodePayHint": "openId blacklist only applies to trades that already have a user identity before pay (e.g. code-plate/aggregate JSAPI or mini). If code-plate uses a method without openId, this list cannot block the user before payment."
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "กำหนดร้านค้าช่องทางและความสามารถ H5/มินิ เฉพาะที่ใช้ ข้ามเส้นทาง",
"atLeastOneRequired": "ตั้งค่าอย่างน้อยหนึ่งสภาพแวดล้อมและรูปแบบ",
"partialRowIncomplete": "ต้องกรอกร้านค้าช่องทางและความสามารถครบ",
"methodModeHint": "กำหนดวิธีชำระ H5/มินิ เฉพาะที่ใช้ ร้านค้าช่องทางตามเส้นทาง"
"methodModeHint": "กำหนดวิธีชำระ H5/มินิ เฉพาะที่ใช้ ร้านค้าช่องทางตามเส้นทาง",
"openIdRiskHint": "When using openId risk blacklist for pre-pay blocking, code-plate environments must use methods that can obtain a user identity (e.g. WeChat JSAPI/mini, Alipay JSAPI). H5 redirect / QR methods without openId cannot be blocked by openId before payment (IP blacklist still works)."
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "ประเภทและค่าไม่สามารถแก้ไขหลังสร้าง",
"expireEmpty": "เว้นว่างหากถาวร"
"expireEmpty": "เว้นว่างหากถาวร",
"openIdCodePayHint": "openId blacklist only applies to trades that already have a user identity before pay (e.g. code-plate/aggregate JSAPI or mini). If code-plate uses a method without openId, this list cannot block the user before payment."
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "Chỉ chỉ định thương nhân kênh và năng lực H5/mini khi cần; bỏ qua định tuyến",
"atLeastOneRequired": "Cần cấu hình ít nhất một môi trường và hình thái",
"partialRowIncomplete": "Thương nhân kênh và năng lực phải điền đủ",
"methodModeHint": "Chỉ cấu hình phương thức H5/mini khi cần; thương nhân kênh theo định tuyến"
"methodModeHint": "Chỉ cấu hình phương thức H5/mini khi cần; thương nhân kênh theo định tuyến",
"openIdRiskHint": "When using openId risk blacklist for pre-pay blocking, code-plate environments must use methods that can obtain a user identity (e.g. WeChat JSAPI/mini, Alipay JSAPI). H5 redirect / QR methods without openId cannot be blocked by openId before payment (IP blacklist still works)."
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "Loại và giá trị không thể sửa sau khi tạo",
"expireEmpty": "Để trống nếu vĩnh viễn"
"expireEmpty": "Để trống nếu vĩnh viễn",
"openIdCodePayHint": "openId blacklist only applies to trades that already have a user identity before pay (e.g. code-plate/aggregate JSAPI or mini). If code-plate uses a method without openId, this list cannot block the user before payment."
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "按需为各环境的 H5、小程序指定通道商户和支付能力可只配部分项跳过通道路由",
"atLeastOneRequired": "请至少配置一项环境与形态",
"partialRowIncomplete": "通道商户与支付能力须同时填写",
"methodModeHint": "按需为各环境的 H5、小程序指定支付方式可只配部分项通道商户跟随通道路由"
"methodModeHint": "按需为各环境的 H5、小程序指定支付方式可只配部分项通道商户跟随通道路由",
"openIdRiskHint": "若使用 openId 类型风控黑名单做支付前拦截,码牌对应环境须配置可获取用户标识的支付方式(如微信 JSAPI/小程序、支付宝 JSAPI 等)。若配置 H5 跳转、主扫等免 openId 方式,将无法在支付前按用户 openId 拦截IP 名单仍有效)。"
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "类型与名单值创建后不可修改",
"expireEmpty": "留空表示永久有效"
"expireEmpty": "留空表示永久有效",
"openIdCodePayHint": "openId 名单仅对下单前已拿到用户标识的交易生效(例如码牌/聚合的 JSAPI、小程序支付。码牌若使用免 openId 的支付方式,该名单无法在支付前拦截该用户。"
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "按需為各環境的 H5、小程序指定通道商戶和支付能力可只配部分項跳過通道路由",
"atLeastOneRequired": "請至少設定一項環境與形態",
"partialRowIncomplete": "通道商戶與支付能力須同時填寫",
"methodModeHint": "按需為各環境的 H5、小程序指定支付方式可只配部分項通道商戶跟隨通道路由"
"methodModeHint": "按需為各環境的 H5、小程序指定支付方式可只配部分項通道商戶跟隨通道路由",
"openIdRiskHint": "若使用 openId 類型風控黑名單做支付前攔截,碼牌對應環境須配置可取得使用者標識的支付方式(如微信 JSAPI/小程式、支付寶 JSAPI 等)。若配置 H5 跳轉、主掃等免 openId 方式,將無法在支付前依使用者 openId 攔截IP 名單仍有效)。"
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "类型与名单值创建后不可修改",
"expireEmpty": "留空表示永久有效"
"expireEmpty": "留空表示永久有效",
"openIdCodePayHint": "openId 名單僅對下單前已取得使用者標識的交易生效(例如碼牌/聚合的 JSAPI、小程式支付。碼牌若使用免 openId 的支付方式,該名單無法在支付前攔截該使用者。"
}
},
"hit": {

View File

@@ -40,6 +40,7 @@
"directModeHint": "按需為各環境的 H5、小程式指定通道商戶和支付能力可只配部分項跳過通道路由",
"atLeastOneRequired": "請至少設定一項環境與形態",
"partialRowIncomplete": "通道商戶與支付能力須同時填寫",
"methodModeHint": "按需為各環境的 H5、小程式指定支付方式可只配部分項通道商戶跟隨通道路由"
"methodModeHint": "按需為各環境的 H5、小程式指定支付方式可只配部分項通道商戶跟隨通道路由",
"openIdRiskHint": "若使用 openId 類型風控黑名單做支付前攔截,碼牌對應環境須配置可取得使用者標識的支付方式(如微信 JSAPI/小程式、支付寶 JSAPI 等)。若配置 H5 跳轉、主掃等免 openId 方式,將無法在支付前依使用者 openId 攔截IP 名單仍有效)。"
}
}

View File

@@ -26,7 +26,8 @@
},
"tip": {
"typeValueImmutable": "类型与名单值创建后不可修改",
"expireEmpty": "留空表示永久有效"
"expireEmpty": "留空表示永久有效",
"openIdCodePayHint": "openId 名單僅對下單前已取得使用者標識的交易生效(例如碼牌/聚合的 JSAPI、小程式支付。碼牌若使用免 openId 的支付方式,該名單無法在支付前攔截該使用者。"
}
},
"hit": {

View File

@@ -290,40 +290,37 @@
return { total, ok, gapLabels };
});
/** DIRECT: 按每行默认 method 拉通道商户 */
/**
* DIRECT: 按商户+渠道列通道商户(不绑默认 JSAPI method与路由直接指定一致
*/
async function loadChannelMchCandidates() {
const map: Record<string, LabelValue[]> = {};
await Promise.all(
CODE_CLIENT_ENVS.flatMap((sc) =>
CODE_PAY_FORMS.map(async (pf) => {
const method = defaultMethodFor(sc.clientEnv, pf);
const key = rowKey(sc.clientEnv, pf);
const { data } = await PayRouteApi.listSceneChannelMchCandidates({
appId: appId.value,
provider: sc.provider,
method,
});
map[key] = data || [];
}),
),
CODE_CLIENT_ENVS.map(async (sc) => {
const { data } = await CodeConfigApi.listDirectChannelMchCandidates({
mchNo: mchNo.value,
provider: sc.provider,
});
const list = data || [];
// 同环境 H5/mini 共用同一批通道商户候选
for (const pf of CODE_PAY_FORMS) {
map[rowKey(sc.clientEnv, pf)] = list;
}
}),
);
channelMchMap.value = map;
}
/**
* DIRECT: 按通道商户列全部已挂载能力(含 H5/主扫),供 needOpenId 与真实能力对齐
*/
async function loadCapabilityForRow(clientEnv: string, payForm: CodePayForm, channelMchNo: string) {
const sc = CODE_CLIENT_ENVS.find((s) => s.clientEnv === clientEnv);
const key = rowKey(clientEnv, payForm);
if (!sc || !channelMchNo) {
if (!channelMchNo) {
capabilityMap.value = { ...capabilityMap.value, [key]: [] };
return;
}
const method = defaultMethodFor(clientEnv, payForm);
const { data } = await PayRouteApi.listSceneCapabilityCandidates({
appId: appId.value,
provider: sc.provider,
method,
channelMchNo,
});
const { data } = await CodeConfigApi.listDirectCapabilityCandidates(channelMchNo);
capabilityMap.value = { ...capabilityMap.value, [key]: data || [] };
}
@@ -534,6 +531,14 @@
<div class="mb-4">
<a-alert :message="modeHint" type="info" show-icon />
</div>
<!-- openId 风控与码牌支付方式关系说明 -->
<div class="mb-4">
<a-alert
:message="$t('payment.merchant.codeConfig.codeConfig.openIdRiskHint')"
type="warning"
show-icon
/>
</div>
<div v-if="showRoutePreview" class="mb-5 flex flex-wrap items-center gap-3 text-sm">
<span class="text-muted-foreground">{{ $t('payment.merchant.codeConfig.codeConfig.currentRouteMode') }}:</span>

View File

@@ -152,6 +152,14 @@
<a-radio-button value="open_id">{{ $t('payment.risk.blacklist.type.open_id') }}</a-radio-button>
</a-radio-group>
</a-form-item>
<!-- openId 类型与码牌 JSAPI 等取 openId 方式的关系提示 -->
<div v-if="formState.type === 'open_id'" class="mb-4 px-2">
<a-alert
:message="$t('payment.risk.blacklist.tip.openIdCodePayHint')"
type="warning"
show-icon
/>
</div>
<!-- 名单值 -->
<a-form-item
:label="$t('payment.risk.blacklist.field.value')"

View File

@@ -150,6 +150,15 @@
<BQuery :fields="queryFields" :query-params="queryForm" @query="queryPage" @reset="resetQuery" />
</a-card>
<!-- openId 名单与码牌支付方式关系说明 -->
<div class="mt-4">
<a-alert
:message="$t('payment.risk.blacklist.tip.openIdCodePayHint')"
type="info"
show-icon
/>
</div>
<div class="mt-4">
<a-card>
<vxe-toolbar ref="xToolbar" custom refresh :refresh-options="{ queryMethod: queryPage }">