Files
sub2api/frontend/src/components/payment/ProviderCard.vue
erio 63d1860dc0 feat(payment): add complete payment system with multi-provider support
Add a full payment and subscription system supporting EasyPay (Alipay/WeChat),
Stripe, and direct Alipay/WeChat Pay providers with multi-instance load balancing.
2026-04-11 13:16:35 +08:00

107 lines
4.5 KiB
Vue

<template>
<div
:class="[
'group relative rounded-lg border transition-all',
enabled ? 'border-gray-200 dark:border-dark-600' : 'border-gray-200 bg-gray-50 opacity-50 dark:border-dark-700 dark:bg-dark-800/50',
]"
:title="!enabled ? t('admin.settings.payment.typeDisabled') + ' — ' + t('admin.settings.payment.enableTypesFirst') : undefined"
>
<div :class="[
'flex items-center justify-between px-4 py-2.5',
!enabled && 'pointer-events-none',
]">
<!-- Left: icon + name + key badge + type badges -->
<div class="flex items-center gap-3">
<div :class="[
'rounded-md p-1.5',
provider.enabled && enabled ? 'bg-green-100 dark:bg-green-900/30' : 'bg-gray-100 dark:bg-dark-700',
]">
<Icon
name="server"
size="sm"
:class="provider.enabled && enabled ? 'text-green-600 dark:text-green-400' : 'text-gray-400'"
/>
</div>
<span class="text-sm font-medium text-gray-900 dark:text-white">{{ provider.name }}</span>
<span class="text-xs text-gray-400 dark:text-gray-500">{{ keyLabel }}</span>
<span v-if="provider.payment_mode" class="text-xs text-gray-400 dark:text-gray-500">· {{ modeLabel }}</span>
<span v-if="enabled && availableTypes.length" class="text-xs text-gray-300 dark:text-gray-600">|</span>
<div v-if="enabled" class="flex items-center gap-1">
<button
v-for="pt in availableTypes"
:key="pt.value"
type="button"
@click="emit('toggleType', pt.value)"
:class="[
'rounded px-2 py-0.5 text-xs font-medium transition-all',
isSelected(pt.value)
? 'bg-primary-500 text-white'
: 'bg-gray-100 text-gray-400 dark:bg-dark-700 dark:text-gray-500',
]"
>{{ pt.label }}</button>
</div>
</div>
<!-- Right: toggles + actions -->
<div class="flex items-center gap-4">
<ToggleSwitch :label="t('common.enabled')" :checked="provider.enabled" @toggle="emit('toggleField', 'enabled')" />
<ToggleSwitch :label="t('admin.settings.payment.refundEnabled')" :checked="provider.refund_enabled" @toggle="emit('toggleField', 'refund_enabled')" />
<div class="flex items-center gap-2 border-l border-gray-200 pl-3 dark:border-dark-600">
<button type="button" @click="emit('edit')" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-blue-50 hover:text-blue-600 dark:hover:bg-blue-900/20 dark:hover:text-blue-400">
<Icon name="edit" size="sm" />
<span class="text-xs">{{ t('common.edit') }}</span>
</button>
<button type="button" @click="emit('delete')" class="flex flex-col items-center gap-0.5 rounded-lg p-1.5 text-gray-500 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/20 dark:hover:text-red-400">
<Icon name="trash" size="sm" />
<span class="text-xs">{{ t('common.delete') }}</span>
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Icon from '@/components/icons/Icon.vue'
import ToggleSwitch from './ToggleSwitch.vue'
import type { ProviderInstance } from '@/types/payment'
import type { TypeOption } from './providerConfig'
import { PAYMENT_MODE_QRCODE, PAYMENT_MODE_POPUP } from './providerConfig'
const PROVIDER_KEY_LABELS: Record<string, string> = {
easypay: 'admin.settings.payment.providerEasypay',
alipay: 'admin.settings.payment.providerAlipay',
wxpay: 'admin.settings.payment.providerWxpay',
stripe: 'admin.settings.payment.providerStripe',
}
const props = defineProps<{
provider: ProviderInstance
enabled: boolean
availableTypes: TypeOption[]
}>()
const emit = defineEmits<{
toggleField: [field: 'enabled' | 'refund_enabled']
toggleType: [type: string]
edit: []
delete: []
}>()
const { t } = useI18n()
const keyLabel = computed(() => t(PROVIDER_KEY_LABELS[props.provider.provider_key] || props.provider.provider_key))
const modeLabel = computed(() => {
if (props.provider.payment_mode === PAYMENT_MODE_QRCODE) return t('admin.settings.payment.modeQRCode')
if (props.provider.payment_mode === PAYMENT_MODE_POPUP) return t('admin.settings.payment.modePopup')
return ''
})
function isSelected(type: string): boolean {
return props.provider.supported_types.includes(type)
}
</script>