接入顺成支付并优化配置列表

This commit is contained in:
yml
2026-06-18 21:33:15 +08:00
parent 40ae14d450
commit 6d385478a6
13 changed files with 1763 additions and 40 deletions
@@ -34,6 +34,11 @@ const defaultGateways: Record<string, string> = {
mock: '',
}
const shunchengGateways: Record<string, string> = {
production: 'http://jyzxapi.ydxnhb.com',
sandbox: 'http://jyzxapi.ydxnhb.com',
}
const lakalaGateways: Record<string, string> = {
production: 'https://s2.lakala.com',
sandbox: 'https://test.wsmsd.cn/sit',
@@ -42,12 +47,14 @@ const lakalaGateways: Record<string, string> = {
const defaultNotifyURLs: Record<string, string> = {
leshua: 'https://hao858.com/api/payments/leshua/notify',
lakala: 'https://hao858.com/api/payments/lakala/notify',
shuncheng: 'https://hao858.com/api/payments/shuncheng/notify',
mock: '',
}
const defaultJumpURLs: Record<string, string> = {
leshua: 'https://hao858.com/orders',
lakala: 'https://hao858.com/orders',
shuncheng: 'https://hao858.com/orders',
mock: '',
}
@@ -66,6 +73,9 @@ function defaultGateway(provider: string, environment = defaultEnvironment(provi
if (provider === 'lakala') {
return lakalaGateways[environment] || lakalaGateways.production
}
if (provider === 'shuncheng') {
return shunchengGateways[environment] || shunchengGateways.production
}
return defaultGateways[provider] || ''
}
@@ -74,6 +84,11 @@ function isDefaultLakalaGateway(value?: string) {
return !value || Object.values(lakalaGateways).includes(value)
}
// isDefaultShunchengGateway 判断当前网关是否仍是顺成系统默认值。
function isDefaultShunchengGateway(value?: string) {
return !value || Object.values(shunchengGateways).includes(value)
}
function defaultExtraConfig(provider: string) {
if (provider === 'lakala') {
return {
@@ -85,6 +100,14 @@ function defaultExtraConfig(provider: string) {
order_expire_minutes: 15,
}
}
if (provider === 'shuncheng') {
return {
secret_id: '',
shop_no: '',
refund_type: '',
token_refresh_margin_seconds: 300,
}
}
return {}
}
@@ -125,8 +148,13 @@ const dialogTitle = computed(() => {
const isReadonly = computed(() => props.mode === 'view')
const isLakala = computed(() => formData.value.provider === 'lakala')
const isShuncheng = computed(() => formData.value.provider === 'shuncheng')
const isMock = computed(() => formData.value.provider === 'mock')
const signKeyLabel = computed(() => (isLakala.value ? '商户私钥' : '签名密钥'))
const signKeyLabel = computed(() => {
if (isLakala.value) return '商户私钥'
if (isShuncheng.value) return 'SecretKey'
return '签名密钥'
})
const notifyKeyLabel = computed(() => (isLakala.value ? '通知证书' : '通知密钥'))
const extraConfig = computed<Record<string, any>>(() => formData.value.extra_config || {})
@@ -200,10 +228,15 @@ function handlePayWayChange(payWay: string | number | boolean) {
syncLakalaPayMode(String(payWay))
}
// handleEnvironmentChange 处理环境切换,并同步拉卡拉默认网关。
// handleEnvironmentChange 处理环境切换,并同步渠道默认网关。
function handleEnvironmentChange(environment: string) {
if (!isLakala.value || !isDefaultLakalaGateway(formData.value.gateway_url)) return
formData.value.gateway_url = defaultGateway('lakala', environment)
if (isLakala.value && isDefaultLakalaGateway(formData.value.gateway_url)) {
formData.value.gateway_url = defaultGateway('lakala', environment)
return
}
if (isShuncheng.value && isDefaultShunchengGateway(formData.value.gateway_url)) {
formData.value.gateway_url = defaultGateway('shuncheng', environment)
}
}
async function handleViewSecrets() {
@@ -284,6 +317,7 @@ function handleClose() {
>
<el-option label="乐刷支付" value="leshua" />
<el-option label="拉卡拉支付" value="lakala" />
<el-option label="顺成支付" value="shuncheng" />
<el-option label="模拟支付" value="mock" />
</el-select>
</el-form-item>
@@ -328,6 +362,14 @@ function handleClose() {
<el-input v-model="extraConfig.term_no" placeholder="拉卡拉终端号" :disabled="isReadonly" />
</el-form-item>
<el-form-item v-if="isShuncheng" label="Secret ID" required>
<el-input
v-model="extraConfig.secret_id"
placeholder="顺成接口调用凭据 SecretId"
:disabled="isReadonly"
/>
</el-form-item>
<el-form-item v-if="!isMock" :label="signKeyLabel" required>
<el-input
v-model="formData.sign_key"
@@ -436,6 +478,37 @@ function handleClose() {
/>
</el-form-item>
<el-form-item v-if="isShuncheng" label="门店编号">
<el-input
v-model="extraConfig.shop_no"
placeholder="可选,顺成商户门店编号"
:disabled="isReadonly"
/>
</el-form-item>
<el-form-item v-if="isShuncheng" label="退款出资">
<el-select
v-model="extraConfig.refund_type"
clearable
placeholder="使用顺成默认规则"
:disabled="isReadonly"
>
<el-option label="顺成默认" value="" />
<el-option label="当日交易抵扣" value="0" />
<el-option label="优先交易抵扣,不足走余额" value="1" />
<el-option label="仅商户余额" value="2" />
</el-select>
</el-form-item>
<el-form-item v-if="isShuncheng" label="Token刷新秒">
<el-input-number
v-model="extraConfig.token_refresh_margin_seconds"
:min="60"
:max="1800"
: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" />
@@ -1,7 +1,7 @@
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import { computed, onMounted, ref } from 'vue'
import { Plus, Refresh, View, Edit, Delete, Download, Upload } from '@element-plus/icons-vue'
import { Plus, Refresh, View, Edit, Delete, Download, Upload, StarFilled } from '@element-plus/icons-vue'
import {
fetchPaymentConfigs,
@@ -37,6 +37,7 @@ const providerOptions = [
{ label: '全部', value: '' },
{ label: '乐刷支付', value: 'leshua' },
{ label: '拉卡拉支付', value: 'lakala' },
{ label: '顺成支付', value: 'shuncheng' },
{ label: '模拟支付', value: 'mock' },
]
@@ -216,6 +217,7 @@ function formatProvider(provider: string) {
const map: Record<string, string> = {
leshua: '乐刷支付',
lakala: '拉卡拉支付',
shuncheng: '顺成支付',
mock: '模拟支付',
}
return map[provider] || provider
@@ -230,13 +232,13 @@ function formatPayWay(payWay: string) {
return map[payWay] || payWay || '-'
}
// getPayWayType 按支付方式返回标签颜色
function getPayWayType(payWay: string) {
const map: Record<string, 'success' | 'primary' | 'info'> = {
ZFBZF: 'primary',
WXZF: 'success',
// getPayWayClass 按支付方式返回标签样式,避免与状态/环境标签混淆
function getPayWayClass(payWay: string) {
const map: Record<string, string> = {
ZFBZF: 'pay-way-alipay',
WXZF: 'pay-way-wechat',
}
return map[payWay] || 'info'
return map[payWay] || 'pay-way-other'
}
function formatStatus(status: string) {
@@ -260,13 +262,30 @@ function formatAmount(amountCent: number) {
return formatCent(amountCent)
}
function getStatusType(status: string) {
const map: Record<string, 'success' | 'info' | 'warning'> = {
active: 'success',
disabled: 'info',
testing: 'warning',
function getEnvironmentClass(env: string) {
const map: Record<string, string> = {
production: 'env-production',
sandbox: 'env-sandbox',
}
return map[status] || 'info'
return map[env] || 'env-other'
}
function getStatusClass(status: string) {
const map: Record<string, string> = {
active: 'config-active',
disabled: 'config-disabled',
testing: 'config-testing',
}
return map[status] || 'config-disabled'
}
function isDefaultConfig(row: PaymentConfig) {
const value = row.is_default as unknown
return value === true || value === 'true' || value === 1
}
function tableRowClassName({ row }: { row: PaymentConfig }) {
return isDefaultConfig(row) ? 'payment-config-row-default' : ''
}
function canDeleteConfig(row: PaymentConfig) {
@@ -357,7 +376,13 @@ function deleteDisabledReason(row: PaymentConfig) {
@change="handleImportFile"
/>
<el-table :data="filteredConfigs" v-loading="loading" class="payment-config-table" stripe>
<el-table
:data="filteredConfigs"
v-loading="loading"
class="payment-config-table"
:row-class-name="tableRowClassName"
stripe
>
<el-table-column prop="name" label="配置名称" min-width="220" show-overflow-tooltip />
<el-table-column prop="provider" label="服务商" width="120">
<template #default="{ row }">
@@ -366,7 +391,7 @@ function deleteDisabledReason(row: PaymentConfig) {
</el-table-column>
<el-table-column prop="pay_way" label="支付方式" width="104" align="center">
<template #default="{ row }">
<el-tag :type="getPayWayType(row.pay_way)" size="small">
<el-tag class="config-tag" :class="getPayWayClass(row.pay_way)" size="small">
{{ formatPayWay(row.pay_way) }}
</el-tag>
</template>
@@ -374,21 +399,25 @@ function deleteDisabledReason(row: PaymentConfig) {
<el-table-column prop="merchant_id" label="商户号" min-width="150" />
<el-table-column prop="environment" label="环境" width="86" align="center">
<template #default="{ row }">
<el-tag :type="row.environment === 'production' ? 'success' : 'warning'" size="small">
<el-tag class="config-tag" :class="getEnvironmentClass(row.environment)" size="small">
{{ formatEnvironment(row.environment) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="86" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)" size="small">
<el-tag class="config-tag" :class="getStatusClass(row.status)" size="small">
{{ formatStatus(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="is_default" label="默认" width="86" align="center">
<el-table-column prop="is_default" label="默认渠道" width="118" align="center">
<template #default="{ row }">
<el-tag v-if="row.is_default" type="primary" size="small">默认</el-tag>
<span v-if="isDefaultConfig(row)" class="default-badge">
<el-icon><StarFilled /></el-icon>
默认渠道
</span>
<span v-else class="standby-badge">备用</span>
</template>
</el-table-column>
<el-table-column label="成功支付" min-width="150">
@@ -524,6 +553,105 @@ function deleteDisabledReason(row: PaymentConfig) {
font-weight: 600;
}
.payment-config-table :deep(.payment-config-row-default td.el-table__cell) {
background: #f8fbff;
}
.payment-config-table :deep(.payment-config-row-default td.el-table__cell:first-child) {
box-shadow: inset 3px 0 0 #2563eb;
}
.config-tag {
min-width: 46px;
justify-content: center;
border-radius: 5px;
font-weight: 600;
}
.config-tag.pay-way-alipay {
color: #1d4ed8;
background: #eff6ff;
border-color: #bfdbfe;
}
.config-tag.pay-way-wechat {
color: #047857;
background: #ecfdf5;
border-color: #a7f3d0;
}
.config-tag.pay-way-other {
color: #475569;
background: #f8fafc;
border-color: #cbd5e1;
}
.config-tag.env-production {
color: #475569;
background: #f8fafc;
border-color: #cbd5e1;
}
.config-tag.env-sandbox {
color: #92400e;
background: #fffbeb;
border-color: #fde68a;
}
.config-tag.env-other {
color: #64748b;
background: #f8fafc;
border-color: #cbd5e1;
}
.config-tag.config-active {
color: #075985;
background: #e0f2fe;
border-color: #7dd3fc;
}
.config-tag.config-disabled {
color: #64748b;
background: #f1f5f9;
border-color: #cbd5e1;
}
.config-tag.config-testing {
color: #92400e;
background: #fffbeb;
border-color: #fde68a;
}
.default-badge,
.standby-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 72px;
height: 24px;
border-radius: 6px;
font-size: 12px;
font-weight: 700;
white-space: nowrap;
}
.default-badge {
gap: 4px;
color: #ffffff;
background: #1d4ed8;
box-shadow: 0 4px 10px rgb(29 78 216 / 0.16);
}
.default-badge .el-icon {
font-size: 13px;
}
.standby-badge {
color: #94a3b8;
background: transparent;
font-weight: 600;
}
.usage-cell {
display: grid;
gap: 4px;
@@ -186,6 +186,7 @@ function jsonText(value: unknown) {
<el-select v-model="filters.provider" clearable placeholder="全部渠道" class="full-control">
<el-option label="拉卡拉" value="lakala" />
<el-option label="乐刷" value="leshua" />
<el-option label="顺成" value="shuncheng" />
<el-option label="Mock" value="mock" />
</el-select>
</el-form-item>