支持拉卡拉多渠道支付和测试充值

This commit is contained in:
yml
2026-06-06 21:46:41 +08:00
parent 4d40883da6
commit ba4c56c0cc
16 changed files with 1917 additions and 157 deletions
@@ -29,23 +29,48 @@ 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 defaultGateways: Record<string, string> = {
leshua: 'https://paygate.leshuazf.com/cgi-bin/lepos_pay_gateway.cgi',
lakala: 'https://test.wsmsd.cn/sit',
mock: '',
}
function defaultExtraConfig(provider: string) {
if (provider === 'lakala') {
return {
app_id: '',
serial_no: '',
term_no: '',
lakala_cert: '',
pay_mode: 'ALIPAY',
order_expire_minutes: 15,
}
}
return {}
}
function makeDefaultForm(provider = 'leshua'): CreatePaymentConfigRequest {
return {
name: '',
provider,
merchant_id: '',
gateway_url: defaultGateways[provider] || '',
sign_key: '',
notify_key: '',
notify_url: '',
jump_url: '',
pay_way: 'ZFBZF',
jspay_flag: '2',
sign_type: provider === 'lakala' ? 'SHA256withRSA' : 'MD5',
extra_config: defaultExtraConfig(provider),
is_default: false,
status: 'active',
environment: provider === 'lakala' ? 'sandbox' : 'production',
business_tags: [],
}
}
const formData = ref<CreatePaymentConfigRequest>(makeDefaultForm())
const dialogTitle = computed(() => {
const titles = {
@@ -57,6 +82,16 @@ const dialogTitle = computed(() => {
})
const isReadonly = computed(() => props.mode === 'view')
const isLakala = computed(() => formData.value.provider === 'lakala')
const isMock = computed(() => formData.value.provider === 'mock')
const signKeyLabel = computed(() => (isLakala.value ? '商户私钥' : '签名密钥'))
const notifyKeyLabel = computed(() => (isLakala.value ? '通知证书' : '通知密钥'))
const extraConfig = computed<Record<string, any>>(() => {
if (!formData.value.extra_config) {
formData.value.extra_config = {}
}
return formData.value.extra_config
})
watch(
() => props.modelValue,
@@ -72,6 +107,7 @@ watch(
pay_way: props.config.pay_way,
jspay_flag: props.config.jspay_flag,
sign_type: props.config.sign_type,
extra_config: props.config.extra_config || defaultExtraConfig(props.config.provider),
is_default: props.config.is_default,
status: props.config.status,
environment: props.config.environment,
@@ -81,23 +117,7 @@ watch(
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: [],
}
formData.value = makeDefaultForm()
showSecrets.value = false
advancedOpen.value = []
}
@@ -105,6 +125,17 @@ watch(
{ immediate: true }
)
function handleProviderChange(provider: string) {
if (props.mode !== 'create') return
const currentName = formData.value.name
const currentNotifyURL = formData.value.notify_url
formData.value = {
...makeDefaultForm(provider),
name: currentName,
notify_url: currentNotifyURL,
}
}
async function handleViewSecrets() {
if (!props.config) return
loadingSecrets.value = true
@@ -138,6 +169,7 @@ async function handleSave() {
pay_way: formData.value.pay_way,
jspay_flag: formData.value.jspay_flag,
sign_type: formData.value.sign_type,
extra_config: formData.value.extra_config,
is_default: formData.value.is_default,
status: formData.value.status,
environment: formData.value.environment,
@@ -175,36 +207,61 @@ function handleClose() {
</el-form-item>
<el-form-item label="支付服务商" required>
<el-select v-model="formData.provider" :disabled="mode !== 'create'">
<el-select v-model="formData.provider" :disabled="mode !== 'create'" @change="handleProviderChange">
<el-option label="乐刷支付" value="leshua" />
<el-option label="拉卡拉支付" value="lakala" />
<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-input v-model="formData.merchant_id" placeholder="渠道商户号" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="网关地址" required>
<el-form-item v-if="!isMock" label="网关地址" required>
<el-input v-model="formData.gateway_url" placeholder="支付网关URL" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="签名密钥" required>
<el-form-item v-if="isLakala" label="App ID" required>
<el-input v-model="extraConfig.app_id" placeholder="拉卡拉开放平台 appId" :disabled="isReadonly" />
</el-form-item>
<el-form-item v-if="isLakala" label="证书序列号" required>
<el-input v-model="extraConfig.serial_no" placeholder="商户证书序列号" :disabled="isReadonly" />
</el-form-item>
<el-form-item v-if="isLakala" label="终端号" required>
<el-input v-model="extraConfig.term_no" placeholder="拉卡拉终端号" :disabled="isReadonly" />
</el-form-item>
<el-form-item v-if="!isMock" :label="signKeyLabel" required>
<el-input
v-model="formData.sign_key"
type="password"
show-password
:placeholder="mode === 'edit' ? '留空则不修改' : '请输入签名密钥'"
:type="isLakala ? 'textarea' : 'password'"
:rows="isLakala ? 5 : undefined"
:show-password="!isLakala"
:placeholder="mode === 'edit' ? '留空则不修改' : isLakala ? '请输入商户私钥 PEM' : '请输入签名密钥'"
:disabled="isReadonly"
/>
</el-form-item>
<el-form-item label="通知密钥" required>
<el-form-item v-if="!isMock" :label="notifyKeyLabel" required>
<el-input
v-model="formData.notify_key"
type="password"
show-password
:placeholder="mode === 'edit' ? '留空则不修改' : '请输入通知密钥'"
:type="isLakala ? 'textarea' : 'password'"
:rows="isLakala ? 5 : undefined"
:show-password="!isLakala"
:placeholder="mode === 'edit' ? '留空则不修改' : isLakala ? '请输入拉卡拉通知验签证书 PEM' : '请输入通知密钥'"
:disabled="isReadonly"
/>
</el-form-item>
<el-form-item v-if="isLakala" label="平台证书">
<el-input
v-model="extraConfig.lakala_cert"
type="textarea"
:rows="4"
placeholder="拉卡拉平台响应验签证书 PEM,可与通知证书一致"
:disabled="isReadonly"
/>
</el-form-item>
@@ -213,7 +270,7 @@ function handleClose() {
<el-button :loading="loadingSecrets" @click="handleViewSecrets">查看密钥</el-button>
</el-form-item>
<el-form-item label="回调地址" required>
<el-form-item v-if="!isMock" label="回调地址" required>
<el-input v-model="formData.notify_url" placeholder="异步通知回调地址" :disabled="isReadonly" />
</el-form-item>
@@ -254,6 +311,18 @@ function handleClose() {
</el-select>
</el-form-item>
<el-form-item v-if="isLakala" label="拉卡拉模式">
<el-select v-model="extraConfig.pay_mode" :disabled="isReadonly">
<el-option label="支付宝" value="ALIPAY" />
<el-option label="微信" value="WECHAT" />
<el-option label="银联" value="UQRCODEPAY" />
</el-select>
</el-form-item>
<el-form-item v-if="isLakala" label="有效期分钟">
<el-input-number v-model="extraConfig.order_expire_minutes" :min="1" :max="1440" :disabled="isReadonly" />
</el-form-item>
<el-form-item label="支付形态">
<el-select v-model="formData.jspay_flag" :disabled="isReadonly">
<el-option label="H5 / JSAPI" value="1" />
@@ -264,6 +333,7 @@ function handleClose() {
<el-form-item label="签名类型">
<el-select v-model="formData.sign_type" :disabled="isReadonly">
<el-option label="MD5" value="MD5" />
<el-option label="SHA256withRSA" value="SHA256withRSA" />
</el-select>
</el-form-item>
</el-collapse-item>
@@ -29,6 +29,7 @@ const filterEnvironment = ref('')
const providerOptions = [
{ label: '全部', value: '' },
{ label: '乐刷支付', value: 'leshua' },
{ label: '拉卡拉支付', value: 'lakala' },
{ label: '模拟支付', value: 'mock' },
]
@@ -91,6 +92,10 @@ function handleEdit(row: PaymentConfig) {
}
async function handleDelete(row: PaymentConfig) {
if (!canDeleteConfig(row)) {
ElMessage.warning('该配置已有交易记录,需要保留用于退款、查询和回调验签')
return
}
try {
await ElMessageBox.confirm(`确定要删除配置"${row.name}"吗?`, '确认删除', {
type: 'warning',
@@ -118,6 +123,7 @@ function handleSaved() {
function formatProvider(provider: string) {
const map: Record<string, string> = {
leshua: '乐刷支付',
lakala: '拉卡拉支付',
mock: '模拟支付',
}
return map[provider] || provider
@@ -152,6 +158,14 @@ function getStatusType(status: string) {
}
return map[status] || 'info'
}
function canDeleteConfig(row: PaymentConfig) {
return row.total_transactions <= 0
}
function deleteDisabledReason(row: PaymentConfig) {
return canDeleteConfig(row) ? '删除配置' : '已有交易记录,需要保留用于退款、查询和回调验签'
}
</script>
<template>
@@ -159,7 +173,7 @@ function getStatusType(status: string) {
<div class="page-header">
<p class="eyebrow">Payment Configs</p>
<h1>支付配置管理</h1>
<p>管理多个支付商户配置支持乐刷支付等多种支付渠道</p>
<p>管理多个支付商户配置支持乐刷拉卡拉等多种支付渠道</p>
</div>
<div class="filter-bar">
@@ -230,9 +244,19 @@ function getStatusType(status: string) {
<el-button class="action-button edit" size="small" :icon="Edit" @click="handleEdit(row)">
编辑
</el-button>
<el-button class="action-button delete" size="small" :icon="Delete" @click="handleDelete(row)">
删除
</el-button>
<el-tooltip :content="deleteDisabledReason(row)" placement="top">
<span>
<el-button
class="action-button delete"
size="small"
:icon="Delete"
:disabled="!canDeleteConfig(row)"
@click="handleDelete(row)"
>
删除
</el-button>
</span>
</el-tooltip>
</div>
</template>
</el-table-column>