feat(payment): tighten gateways and implement Epay

Keep only balance, Alipay, WeChat, PayPal, Stripe, Epay, and card-pass
channels; remove placeholder aggregators. Add dual-channel Epay gateway
with MD5 notify/return, register cashier methods, and gate card-pass
redeem behind the payment channel switch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 12:59:28 +08:00
co-authored by Claude Fable 5
parent aa4bdfb5c4
commit 723dd180e4
11 changed files with 470 additions and 264 deletions
@@ -2,10 +2,11 @@
<PaymentSectionPage
section="gateway"
title="收款接口"
desc="管理支付网关、查看连接状态、配置回调口令。"
note="当前支持人工确认与测试联调;回调校验口令在接入真实网关时直接复用。"
desc="管理余额、支付宝、微信、PayPal、Stripe、易支付与卡密兑换通道。"
note="以渠道开关与凭据是否齐全为准;异步回调由各网关验签。卡密为兑换入口,不进入收银台。"
:links="[
{ label: '余额充值', path: '/payment/recharge' },
{ label: '充值卡', path: '/payment/card-pass' },
{ label: '付费内容', path: '/payment/paid' },
]"
>
@@ -16,7 +17,7 @@
<n-grid-item v-for="gw in gateways" :key="gw.id">
<div class="aksrt-gateway__card">
<div class="aksrt-gateway__icon" :class="gw.configured ? 'aksrt-gateway__icon--active' : ''">
<i :class="gw.id === 'balance' ? 'fas fa-wallet' : 'fas fa-credit-card'"></i>
<i :class="iconFor(gw.id)"></i>
</div>
<div>
<strong>{{ gw.name }}</strong>
@@ -31,39 +32,8 @@
<n-empty v-if="!loading && gateways.length === 0" description="暂无已注册的支付网关" />
</n-card>
<n-card class="aksrt-section-card" title="运行摘要">
<n-grid :cols="4" :x-gap="12" :y-gap="12">
<n-grid-item>
<div class="aksrt-gateway__stat">
<strong>{{ modeLabel }}</strong>
<p>当前模式</p>
</div>
</n-grid-item>
<n-grid-item>
<div class="aksrt-gateway__stat">
<strong :class="overview.gateway?.test_enabled ? 'aksrt-text-warning' : ''">
{{ overview.gateway?.test_enabled ? '启用' : '关闭' }}
</strong>
<p>测试网关</p>
</div>
</n-grid-item>
<n-grid-item>
<div class="aksrt-gateway__stat">
<strong :class="overview.gateway?.manual_review ? 'aksrt-text-info' : ''">
{{ overview.gateway?.manual_review ? '启用' : '关闭' }}
</strong>
<p>人工复核</p>
</div>
</n-grid-item>
<n-grid-item>
<div class="aksrt-gateway__stat">
<n-tag :type="overview.gateway?.callback_secret ? 'success' : 'warning'" size="small" round>
{{ overview.gateway?.callback_secret ? '已设置' : '未设置' }}
</n-tag>
<p>回调口令</p>
</div>
</n-grid-item>
</n-grid>
<n-card v-if="overview.help_text" class="aksrt-section-card" title="运行说明">
<p class="aksrt-gateway__help">{{ overview.help_text }}</p>
</n-card>
</n-spin>
</template>
@@ -71,7 +41,7 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
import { useMessage } from 'naive-ui'
import PaymentSectionPage from '@/pages/payment/PaymentSectionPage.vue'
import api from '@/api'
@@ -81,12 +51,14 @@ const gateways = ref([])
const overview = ref({})
const loading = ref(false)
const modeLabel = computed(() => {
const mode = overview.value.gateway?.mode
if (mode === 'test_gateway') return '测试网关'
if (mode === 'adapter_ready') return '适配器就绪'
return '人工确认'
})
function iconFor(id) {
if (id === 'balance') return 'fas fa-wallet'
if (id === 'alipay' || id === 'epay_alipay') return 'fab fa-alipay'
if (id === 'wechatpay' || id === 'epay_wxpay') return 'fab fa-weixin'
if (id === 'paypal') return 'fab fa-paypal'
if (id === 'stripe') return 'fab fa-stripe-s'
return 'fas fa-credit-card'
}
onMounted(async () => {
loading.value = true
@@ -98,7 +70,9 @@ onMounted(async () => {
if (gwRes?.data?.gateways) {
gateways.value = gwRes.data.gateways
}
if (ovRes?.data) {
if (ovRes?.data?.gateway) {
overview.value = ovRes.data.gateway
} else if (ovRes?.data) {
overview.value = ovRes.data
}
} catch {
@@ -149,31 +123,10 @@ onMounted(async () => {
color: var(--color-ink-4, #8a909c);
}
.aksrt-gateway__stat {
text-align: center;
padding: 16px;
border-radius: 8px;
background: var(--surface-2, #fafafa);
}
.aksrt-gateway__stat strong {
display: block;
font-size: 18px;
font-weight: 600;
color: var(--color-ink, #1a1a2e);
}
.aksrt-gateway__stat p {
margin: 4px 0 0;
font-size: 12px;
color: var(--color-ink-4, #8a909c);
}
.aksrt-text-warning {
color: var(--color-warning, #f59e0b);
}
.aksrt-text-info {
color: var(--color-info, #3b82f6);
.aksrt-gateway__help {
margin: 0;
font-size: 13px;
color: var(--color-ink-3, #5c6370);
line-height: 1.6;
}
</style>
@@ -2,25 +2,15 @@ export const gatewaySchema = {
key: 'payment.gateway',
title: '收款接口',
icon: 'fas fa-credit-card',
description: '配置支付网关凭据、回调校验口令与运行模式。开启渠道后展开填写配置,可随时收起。',
description: '管理已支持的收款通道:余额、支付宝、微信、PayPal、Stripe、易支付与卡密兑换。开启渠道后展开填写凭据。',
defaults: {
payment_gateway_mode: 'manual_review',
payment_gateway_test_enabled: true,
payment_gateway_manual_review: true,
payment_gateway_callback_secret: '',
payment_gateway_help: '未接真实支付网关时,建议使用测试网关或人工确认模式完成联调。',
payment_wechat_enabled: false,
payment_alipay_enabled: false,
payment_paypal_enabled: false,
payment_stripe_enabled: false,
payment_epay_enabled: false,
payment_balance_enabled: true,
payment_card_pass_enabled: false,
payment_xunhupay_enabled: false,
payment_xhpay_enabled: false,
payment_payjs_enabled: false,
payment_epay_enabled: false,
payment_vmq_enabled: false,
payment_codepay_enabled: false,
payment_stripe_enabled: false,
wechat_merchantid: '',
wechat_appid: '',
wechat_payment_type: 'native',
@@ -37,53 +27,46 @@ export const gatewaySchema = {
paypal_client_secret: '',
paypal_currency: 'USD',
paypal_sandbox: true,
xunhupay_appid: '',
xunhupay_appsecret: '',
xhpay_mchid: '',
xhpay_alipay_v2: '',
xhpay_wx_h5: '',
payjs_mchid: '',
payjs_key: '',
epay_apiurl: '',
epay_pid: '',
epay_key: '',
stripe_secret_key: '',
stripe_publishable_key: '',
stripe_webhook_secret: '',
stripe_currency: 'usd',
stripe_test_mode: true,
epay_apiurl: '',
epay_pid: '',
epay_key: '',
epay_channel_alipay: true,
epay_channel_wxpay: true,
epay_device: 'auto',
},
groups: [
{
key: 'adapter',
title: '网关适配器',
icon: 'fas fa-plug',
key: 'notice',
title: '说明',
icon: 'fas fa-info-circle',
fields: [
{ key: 'payment_gateway_mode', label: '运行模式', type: 'radio', default: 'manual_review', options: [{ label: '人工确认', value: 'manual_review' }, { label: '测试网关', value: 'test_gateway' }, { label: '适配器就绪', value: 'adapter_ready' }] },
{ key: 'payment_gateway_test_enabled', label: '启用测试网关', type: 'switch', default: true },
{ key: 'payment_gateway_manual_review', label: '保留人工复核', type: 'switch', default: true },
{ key: 'payment_gateway_callback_secret', label: '回调校验口令', type: 'input', default: '', help: '真实网关接入时可直接复用该口令字段。' },
{ key: 'payment_gateway_help', label: '运维说明', type: 'textarea', default: '未接真实支付网关时,建议使用测试网关或人工确认模式完成联调。' },
{
key: 'payment_gateway_notice',
label: '通道说明',
type: 'notice',
alertType: 'info',
alertContent: '以渠道开关 + 凭据是否齐全为准。异步回调由各网关验签(支付宝/微信/Stripe/易支付等),无需额外口令。卡密为兑换入口,不出现在收银台支付方式列表。',
},
],
},
{
key: 'gateways',
title: '支付渠道开关',
icon: 'fas fa-toggle-on',
description: '先开启渠道,下方对应配置卡片会展开;填写凭据后网关状态变为可用。无凭据的内置渠道(余额/卡密)仅开关即可。',
description: '先开启渠道,下方对应配置卡片会展开;填写凭据后网关状态变为可用。余额与卡密无第三方凭据,仅开关即可。',
fields: [
{ key: 'payment_wechat_enabled', label: '微信支付', type: 'switch', default: false },
{ key: 'payment_balance_enabled', label: '余额支付', type: 'switch', default: true, help: '需同时在「余额充值」启用余额功能。关闭后用户中心仍可有钱包,但结账不可用余额支付。' },
{ key: 'payment_alipay_enabled', label: '支付宝', type: 'switch', default: false },
{ key: 'payment_wechat_enabled', label: '微信支付', type: 'switch', default: false },
{ key: 'payment_paypal_enabled', label: 'PayPal', type: 'switch', default: false },
{ key: 'payment_stripe_enabled', label: 'Stripe', type: 'switch', default: false },
{ key: 'payment_xunhupay_enabled', label: '虎皮椒', type: 'switch', default: false },
{ key: 'payment_xhpay_enabled', label: '小火付', type: 'switch', default: false },
{ key: 'payment_payjs_enabled', label: 'PayJS', type: 'switch', default: false },
{ key: 'payment_epay_enabled', label: '易支付', type: 'switch', default: false },
{ key: 'payment_vmq_enabled', label: 'V免签', type: 'switch', default: false, help: '当前仅开关占位,凭据接入后补齐。' },
{ key: 'payment_codepay_enabled', label: 'Codepay', type: 'switch', default: false, help: '当前仅开关占位,凭据接入后补齐。' },
{ key: 'payment_balance_enabled', label: '余额支付', type: 'switch', default: true, help: '需同时在「余额充值」启用余额功能。关闭后用户中心仍可有钱包,但结账不可用余额支付。' },
{ key: 'payment_card_pass_enabled', label: '卡密充值', type: 'switch', default: false, help: '卡密规则在「卡密设置」;此处仅控制是否作为收款渠道展示。' },
{ key: 'payment_epay_enabled', label: '易支付', type: 'switch', default: false, help: '兼容彩虹易支付协议的聚合收银台,可同时开启支付宝/微信子通道。' },
{ key: 'payment_card_pass_enabled', label: '卡密充值', type: 'switch', default: false, help: '仅控制卡密兑换入口是否开放;卡密规则与批量发卡在「充值卡」页。不进入结账支付方式列表。' },
],
},
{
@@ -110,7 +93,7 @@ export const gatewaySchema = {
key: 'alipay',
title: '支付宝配置',
icon: 'fab fa-alipay',
description: '开启「支付宝」后填写应用与回调地址。',
description: '开启「支付宝」后填写应用与证书。',
collapsible: true,
expandWhen: 'payment_alipay_enabled',
dependsOn: { key: 'payment_alipay_enabled', value: true },
@@ -158,58 +141,26 @@ export const gatewaySchema = {
{ key: 'stripe_test_mode', label: '测试模式', type: 'switch', default: true },
],
},
{
key: 'xunhupay',
title: '虎皮椒配置',
icon: 'fas fa-pepper-hot',
description: '开启「虎皮椒」后填写 App 凭据。',
collapsible: true,
expandWhen: 'payment_xunhupay_enabled',
dependsOn: { key: 'payment_xunhupay_enabled', value: true },
fields: [
{ key: 'xunhupay_appid', label: 'AppID', type: 'input', default: '' },
{ key: 'xunhupay_appsecret', label: 'AppSecret', type: 'input', default: '' },
],
},
{
key: 'xhpay',
title: '小火付配置',
icon: 'fas fa-fire',
description: '开启「小火付」后填写商户与通道参数。',
collapsible: true,
expandWhen: 'payment_xhpay_enabled',
dependsOn: { key: 'payment_xhpay_enabled', value: true },
fields: [
{ key: 'xhpay_mchid', label: '商户号', type: 'input', default: '' },
{ key: 'xhpay_alipay_v2', label: '支付宝V2', type: 'input', default: '' },
{ key: 'xhpay_wx_h5', label: '微信H5', type: 'input', default: '' },
],
},
{
key: 'payjs',
title: 'PayJS 配置',
icon: 'fab fa-js',
description: '开启「PayJS」后填写商户号与通信密钥。',
collapsible: true,
expandWhen: 'payment_payjs_enabled',
dependsOn: { key: 'payment_payjs_enabled', value: true },
fields: [
{ key: 'payjs_mchid', label: '商户号', type: 'input', default: '' },
{ key: 'payjs_key', label: '通信密钥', type: 'input', default: '' },
],
},
{
key: 'epay',
title: '易支付配置',
icon: 'fas fa-university',
description: '开启「易支付」后填写接口与商户信息。',
description: '开启「易支付」后填写接口地址与商户信息,并选择支付宝/微信子通道。',
collapsible: true,
expandWhen: 'payment_epay_enabled',
dependsOn: { key: 'payment_epay_enabled', value: true },
fields: [
{ key: 'epay_apiurl', label: '接口地址', type: 'input', default: '' },
{ key: 'epay_apiurl', label: '接口地址', type: 'input', default: '', help: '易支付站点根地址,如 https://pay.example.com/(末尾斜杠可选)。' },
{ key: 'epay_pid', label: '商户ID', type: 'input', default: '' },
{ key: 'epay_key', label: '商户密钥', type: 'input', default: '' },
{ key: 'epay_key', label: '商户密钥', type: 'input', default: '', help: 'MD5 通信密钥。' },
{ key: 'epay_channel_alipay', label: '启用支付宝通道', type: 'switch', default: true },
{ key: 'epay_channel_wxpay', label: '启用微信通道', type: 'switch', default: true },
{ key: 'epay_device', label: '下单终端', type: 'select', default: 'auto', options: [
{ label: '自动(按 UA)', value: 'auto' },
{ label: '电脑端 submit', value: 'pc' },
{ label: '移动端 mapi', value: 'mobile' },
] },
{ key: 'epay_notify_help', label: '回调地址说明', type: 'notice', alertType: 'info', alertContent: '异步/同步回调固定为 /wp-json/aksrt/v1/payment/epay_alipay/{notify|return} 与 /payment/epay_wxpay/{notify|return},请在易支付后台放行对应 URL。' },
],
},
],