refactor: replace direct WeChat/Alipay with unified pay-api gateway

Switch from direct WeChat Pay / Alipay integrations to the unified
宇之然 pay-api gateway (HMAC-SHA256 auth). Removes wechat_pay.py,
keeps PaymentGateway abstraction, adds UnifiedPayService. Simplifies
payment.py create_order to {plan, pay_type} params. Single webhook
endpoint replaces separate WeChat/Alipay notify handlers.
This commit is contained in:
TradeMate Dev
2026-05-29 18:36:50 +08:00
parent 5d2bced39f
commit 3e39cf0170
34 changed files with 973 additions and 424 deletions
+60 -3
View File
@@ -66,14 +66,47 @@
</main>
<footer class="footer">
<span>TradeMate &copy; {{ new Date().getFullYear() }}</span>
<div class="footer-content">
<div class="footer-section">
<div class="footer-brand">TradeMate</div>
<p class="footer-tagline">AI 外贸小助手 · 让外贸更简单</p>
<div class="qrcode-row">
<div class="qrcode-item">
<img src="/images/yzr/yuzhiran.jpg" alt="微信公众号" class="qrcode-img" />
<span>微信公众号</span>
</div>
<div class="qrcode-item">
<img src="/images/yzr/yuzhiran-tech.jpg" alt="微信服务号" class="qrcode-img" />
<span>微信服务号</span>
</div>
<div class="qrcode-item">
<img src="/images/yzr/yuzhiran-yhl.jpg" alt="小程序" class="qrcode-img" />
<span>小程序</span>
</div>
<div class="qrcode-item">
<img src="/images/yzr/kefu.png" alt="客服" class="qrcode-img" />
<span>微信客服</span>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; {{ new Date().getFullYear() }} TradeMate 外贸小助手. 保留所有权利.</p>
<div class="footer-links">
<a href="http://beian.miit.gov.cn/" target="_blank">{{ beianInfo.icp }}</a>
<a v-if="beianInfo.showGongan" :href="beianInfo.gonganLink" target="_blank" rel="noreferrer" class="gongan-link">
<img src="/images/beian/gongan-beian.png" alt="公安备案" class="gongan-icon" />
{{ beianInfo.gongan }}
</a>
</div>
</div>
</div>
</footer>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
import { getUnreadCount } from '@/api'
@@ -85,6 +118,17 @@ const collapsed = ref(false)
const showMobileMenu = ref(false)
const unread = ref(0)
const beianInfo = computed(() => {
const hostname = window.location.hostname
if (hostname === 'yuzhiran.com' || hostname === 'www.yuzhiran.com') {
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
}
if (hostname === 'yuzhiran.com.cn' || hostname === 'www.yuzhiran.com.cn') {
return { icp: '京ICP备2026007249号-2', gongan: '京公网安备11011502039622号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039622', showGongan: true }
}
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
})
onMounted(async () => {
try {
const res = await getUnreadCount()
@@ -114,7 +158,20 @@ function handleLogout() {
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.notif-badge :deep(.el-badge__content) { top: 8px; right: 4px; }
.content { flex: 1; padding: 24px; overflow-y: auto; background: #f5f5f5; }
.footer { text-align: center; padding: 12px; color: #999; font-size: 12px; border-top: 1px solid #e8e8e8; background: #fff; flex-shrink: 0; }
.footer { text-align: center; color: #999; font-size: 12px; border-top: 1px solid #e8e8e8; background: #fff; flex-shrink: 0; }
.footer-content { padding: 16px 24px 12px; }
.footer-section { margin-bottom: 12px; }
.footer-brand { font-size: 14px; font-weight: 700; color: #1890ff; margin-bottom: 2px; }
.footer-tagline { color: #999; font-size: 11px; margin-bottom: 10px; }
.qrcode-row { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; }
.qrcode-item { display: flex; flex-direction: column; align-items: center; gap: 3px; color: #999; font-size: 10px; }
.qrcode-img { width: 44px; height: 44px; border-radius: 6px; }
.footer-bottom { border-top: 1px solid #eee; padding-top: 10px; display: flex; justify-content: center; align-items: center; gap: 14px; flex-wrap: wrap; }
.footer-links { display: flex; gap: 14px; align-items: center; }
.footer-links a { color: #999; text-decoration: none; }
.footer-links a:hover { color: #1890ff; }
.gongan-link { display: inline-flex; align-items: center; gap: 4px; }
.gongan-icon { height: 14px; vertical-align: middle; }
@media (max-width: 768px) {
.sidebar { position: fixed; left: -220px; top: 0; bottom: 0; z-index: 1000; transition: left 0.3s; }
+62 -2
View File
@@ -75,7 +75,42 @@
</section>
<footer class="landing-footer">
<span>TradeMate 外贸小助手 &copy; {{ new Date().getFullYear() }}</span>
<div class="footer-inner">
<div class="footer-top">
<div class="footer-info">
<div class="footer-brand">TradeMate</div>
<p class="footer-desc">AI 外贸小助手 · 让外贸更简单</p>
</div>
<div class="qrcode-row">
<div class="qrcode-item">
<img src="/images/yzr/yuzhiran.jpg" alt="微信公众号" class="qrcode-img" />
<span>公众号</span>
</div>
<div class="qrcode-item">
<img src="/images/yzr/yuzhiran-tech.jpg" alt="微信服务号" class="qrcode-img" />
<span>服务号</span>
</div>
<div class="qrcode-item">
<img src="/images/yzr/yuzhiran-yhl.jpg" alt="小程序" class="qrcode-img" />
<span>小程序</span>
</div>
<div class="qrcode-item">
<img src="/images/yzr/kefu.png" alt="客服" class="qrcode-img" />
<span>客服</span>
</div>
</div>
</div>
<div class="footer-bottom">
<p>&copy; {{ new Date().getFullYear() }} TradeMate 外贸小助手. 保留所有权利.</p>
<div class="footer-links">
<a href="http://beian.miit.gov.cn/" target="_blank">{{ beianInfo.icp }}</a>
<a v-if="beianInfo.showGongan" :href="beianInfo.gonganLink" target="_blank" rel="noreferrer" class="gongan-link">
<img src="/images/beian/gongan-beian.png" alt="公安备案" class="gongan-icon" />
{{ beianInfo.gongan }}
</a>
</div>
</div>
</div>
</footer>
</div>
</template>
@@ -87,6 +122,17 @@ import { useAuthStore } from '@/stores/auth'
import { register as registerApi } from '@/api'
import { ElMessage } from 'element-plus'
const beianInfo = computed(() => {
const hostname = window.location.hostname
if (hostname === 'yuzhiran.com' || hostname === 'www.yuzhiran.com') {
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
}
if (hostname === 'yuzhiran.com.cn' || hostname === 'www.yuzhiran.com.cn') {
return { icp: '京ICP备2026007249号-2', gongan: '京公网安备11011502039622号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039622', showGongan: true }
}
return { icp: '京ICP备2026007249号-1', gongan: '京公网安备11011502039545号', gonganLink: 'https://beian.mps.gov.cn/#/query/webSearch?code=11011502039545', showGongan: true }
})
const router = useRouter()
const route = useRoute()
const auth = useAuthStore()
@@ -195,7 +241,21 @@ function goWorkspace() { router.push('/workspace') }
.feature-icon { width: 52px; height: 52px; border-radius: 12px; display: flex; align-items: center; justify-content: center; margin-bottom: 16px; }
.feature-card h3 { font-size: 16px; margin-bottom: 8px; color: #333; }
.feature-card p { font-size: 13px; color: #999; line-height: 1.5; }
.landing-footer { text-align: center; padding: 24px; color: #999; font-size: 12px; margin-top: auto; border-top: 1px solid #e8e8e8; background: #fff; }
.landing-footer { text-align: center; color: #999; font-size: 12px; margin-top: auto; border-top: 1px solid #e8e8e8; background: #fff; padding: 24px 20px 16px; }
.footer-inner { max-width: 1200px; margin: 0 auto; }
.footer-top { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; margin-bottom: 16px; }
.footer-info { text-align: left; }
.footer-brand { font-size: 18px; font-weight: 700; color: #1890ff; margin-bottom: 4px; }
.footer-desc { color: #999; font-size: 13px; }
.qrcode-row { display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; }
.qrcode-item { display: flex; flex-direction: column; align-items: center; gap: 4px; color: #999; font-size: 11px; }
.qrcode-img { width: 60px; height: 60px; border-radius: 8px; }
.footer-bottom { border-top: 1px solid #eee; padding-top: 14px; display: flex; justify-content: center; align-items: center; gap: 16px; flex-wrap: wrap; }
.footer-links { display: flex; gap: 16px; align-items: center; }
.footer-links a { color: #999; text-decoration: none; }
.footer-links a:hover { color: #1890ff; }
.gongan-link { display: inline-flex; align-items: center; gap: 4px; }
.gongan-icon { height: 16px; vertical-align: middle; }
@media (max-width: 768px) {
.hero-inner { flex-direction: column; padding: 40px 20px; }