支持后台支付配置管理

This commit is contained in:
yml
2026-06-06 17:49:22 +08:00
parent 13543b3dcb
commit 4d40883da6
29 changed files with 3173 additions and 198 deletions
@@ -0,0 +1,314 @@
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { computed, ref, watch } from 'vue'
import {
createPaymentConfig,
updatePaymentConfig,
fetchPaymentConfig,
type PaymentConfig,
type CreatePaymentConfigRequest,
type UpdatePaymentConfigRequest,
} from '@/features/admin/api/paymentConfig'
import { readError } from '@/utils/error'
const props = defineProps<{
modelValue: boolean
mode: 'create' | 'edit' | 'view'
config: PaymentConfig | null
}>()
const emit = defineEmits<{
(e: 'update:modelValue', val: boolean): void
(e: 'close'): void
(e: 'saved'): void
}>()
const submitting = ref(false)
const showSecrets = ref(false)
const loadingSecrets = ref(false)
const advancedOpen = ref<string[]>([])
const formData = ref<CreatePaymentConfigRequest>({
name: '',
provider: 'leshua',
merchant_id: '',
gateway_url: 'https://paygate.leshuazf.com/cgi-bin/lepos_pay_gateway.cgi',
sign_key: '',
notify_key: '',
notify_url: '',
jump_url: '',
pay_way: 'ZFBZF',
jspay_flag: '2',
sign_type: 'MD5',
is_default: false,
status: 'active',
environment: 'production',
business_tags: [],
})
const dialogTitle = computed(() => {
const titles = {
create: '新增支付配置',
edit: '编辑支付配置',
view: '查看支付配置',
}
return titles[props.mode]
})
const isReadonly = computed(() => props.mode === 'view')
watch(
() => props.modelValue,
(val) => {
if (val && props.config) {
formData.value = {
name: props.config.name,
provider: props.config.provider,
merchant_id: props.config.merchant_id,
gateway_url: props.config.gateway_url,
notify_url: props.config.notify_url,
jump_url: props.config.jump_url,
pay_way: props.config.pay_way,
jspay_flag: props.config.jspay_flag,
sign_type: props.config.sign_type,
is_default: props.config.is_default,
status: props.config.status,
environment: props.config.environment,
business_tags: props.config.business_tags || [],
}
showSecrets.value = false
advancedOpen.value = []
} else if (val && !props.config) {
// 重置表单
formData.value = {
name: '',
provider: 'leshua',
merchant_id: '',
gateway_url: 'https://paygate.leshuazf.com/cgi-bin/lepos_pay_gateway.cgi',
sign_key: '',
notify_key: '',
notify_url: '',
jump_url: '',
pay_way: 'ZFBZF',
jspay_flag: '2',
sign_type: 'MD5',
is_default: false,
status: 'active',
environment: 'production',
business_tags: [],
}
showSecrets.value = false
advancedOpen.value = []
}
},
{ immediate: true }
)
async function handleViewSecrets() {
if (!props.config) return
loadingSecrets.value = true
try {
const config = await fetchPaymentConfig(props.config.id, true)
formData.value.sign_key = config.sign_key || ''
formData.value.notify_key = config.notify_key || ''
showSecrets.value = true
ElMessage.success('密钥已加载')
} catch (error) {
ElMessage.error(readError(error, '加载密钥失败'))
} finally {
loadingSecrets.value = false
}
}
async function handleSave() {
submitting.value = true
try {
if (props.mode === 'create') {
await createPaymentConfig(formData.value)
ElMessage.success('创建成功')
} else if (props.mode === 'edit') {
if (!props.config) return
const payload: UpdatePaymentConfigRequest = {
name: formData.value.name,
merchant_id: formData.value.merchant_id,
gateway_url: formData.value.gateway_url,
notify_url: formData.value.notify_url,
jump_url: formData.value.jump_url,
pay_way: formData.value.pay_way,
jspay_flag: formData.value.jspay_flag,
sign_type: formData.value.sign_type,
is_default: formData.value.is_default,
status: formData.value.status,
environment: formData.value.environment,
business_tags: formData.value.business_tags,
}
// 只有在修改了密钥时才传递
if (formData.value.sign_key) {
payload.sign_key = formData.value.sign_key
}
if (formData.value.notify_key) {
payload.notify_key = formData.value.notify_key
}
await updatePaymentConfig(props.config.id, payload)
ElMessage.success('更新成功')
}
emit('saved')
} catch (error) {
ElMessage.error(readError(error, '保存失败'))
} finally {
submitting.value = false
}
}
function handleClose() {
emit('update:modelValue', false)
emit('close')
}
</script>
<template>
<el-dialog :model-value="modelValue" :title="dialogTitle" width="700px" @close="handleClose">
<el-form :model="formData" label-width="120px">
<el-form-item label="配置名称" required>
<el-input v-model="formData.name" placeholder="如:乐刷生产商户1" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="支付服务商" required>
<el-select v-model="formData.provider" :disabled="mode !== 'create'">
<el-option label="乐刷支付" value="leshua" />
<el-option label="模拟支付" value="mock" />
</el-select>
</el-form-item>
<el-form-item label="商户号" required>
<el-input v-model="formData.merchant_id" placeholder="乐刷商户号" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="网关地址" required>
<el-input v-model="formData.gateway_url" placeholder="支付网关URL" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="签名密钥" required>
<el-input
v-model="formData.sign_key"
type="password"
show-password
:placeholder="mode === 'edit' ? '留空则不修改' : '请输入签名密钥'"
:disabled="isReadonly"
/>
</el-form-item>
<el-form-item label="通知密钥" required>
<el-input
v-model="formData.notify_key"
type="password"
show-password
:placeholder="mode === 'edit' ? '留空则不修改' : '请输入通知密钥'"
:disabled="isReadonly"
/>
</el-form-item>
<el-form-item v-if="mode === 'view' && !showSecrets" label="">
<el-button :loading="loadingSecrets" @click="handleViewSecrets">查看密钥</el-button>
</el-form-item>
<el-form-item label="回调地址" required>
<el-input v-model="formData.notify_url" placeholder="异步通知回调地址" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="是否默认">
<el-switch v-model="formData.is_default" :disabled="isReadonly" />
<span class="form-hint">每个服务商只能有一个默认配置</span>
</el-form-item>
<el-form-item label="状态">
<el-select v-model="formData.status" :disabled="isReadonly">
<el-option label="启用" value="active" />
<el-option label="禁用" value="disabled" />
<el-option label="测试中" value="testing" />
</el-select>
</el-form-item>
<el-form-item label="环境">
<el-select v-model="formData.environment" :disabled="isReadonly">
<el-option label="生产环境" value="production" />
<el-option label="测试环境" value="sandbox" />
</el-select>
</el-form-item>
<el-collapse v-model="advancedOpen" class="advanced-config">
<el-collapse-item name="advanced">
<template #title>
<span class="advanced-title">高级配置</span>
</template>
<el-form-item label="跳转地址">
<el-input v-model="formData.jump_url" placeholder="支付完成跳转地址" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="支付方式">
<el-select v-model="formData.pay_way" :disabled="isReadonly">
<el-option label="支付宝" value="ZFBZF" />
<el-option label="微信" value="WXZF" />
</el-select>
</el-form-item>
<el-form-item label="支付形态">
<el-select v-model="formData.jspay_flag" :disabled="isReadonly">
<el-option label="H5 / JSAPI" value="1" />
<el-option label="简易支付 / 收银台" value="2" />
</el-select>
</el-form-item>
<el-form-item label="签名类型">
<el-select v-model="formData.sign_type" :disabled="isReadonly">
<el-option label="MD5" value="MD5" />
</el-select>
</el-form-item>
</el-collapse-item>
</el-collapse>
</el-form>
<template #footer>
<el-button @click="handleClose">{{ isReadonly ? '关闭' : '取消' }}</el-button>
<el-button v-if="!isReadonly" type="primary" :loading="submitting" @click="handleSave">
保存
</el-button>
</template>
</el-dialog>
</template>
<style scoped>
.advanced-config {
margin-top: 4px;
border-top: 1px solid #edf0f5;
border-bottom: 0;
}
.advanced-config :deep(.el-collapse-item__header) {
height: 44px;
padding-left: 120px;
border-bottom: 0;
color: #4b5563;
font-weight: 600;
}
.advanced-config :deep(.el-collapse-item__wrap) {
border-bottom: 0;
}
.advanced-config :deep(.el-collapse-item__content) {
padding-bottom: 0;
}
.advanced-title {
font-size: 14px;
}
.form-hint {
margin-left: 8px;
color: #6b7280;
font-size: 12px;
}
</style>