176 lines
5.8 KiB
Plaintext
176 lines
5.8 KiB
Plaintext
<template>
|
||
<view class="subscribe-checkout">
|
||
<view class="header">
|
||
<text class="title">确认订阅</text>
|
||
</view>
|
||
|
||
<view v-if="loading" class="loading">加载中...</view>
|
||
<view v-else-if="plan == null" class="empty">未找到订阅方案</view>
|
||
<view v-else class="card">
|
||
<view class="row">
|
||
<text class="label">方案</text>
|
||
<text class="value">{{ plan['name'] }}</text>
|
||
</view>
|
||
<view class="row">
|
||
<text class="label">价格</text>
|
||
<text class="value">¥{{ plan['price'] }} / {{ plan['billing_period'] === 'yearly' ? '年' : '月' }}</text>
|
||
</view>
|
||
<view class="row" v-if="trialDays > 0">
|
||
<text class="label">试用期</text>
|
||
<text class="value">{{ trialDays }} 天</text>
|
||
</view>
|
||
|
||
<view class="section-title">支付方式</view>
|
||
<view class="pay-methods">
|
||
<label class="pay-item" @click="selPay(1)">
|
||
<radio :checked="payMethod === 1"></radio>
|
||
<text>微信支付</text>
|
||
</label>
|
||
<label class="pay-item" @click="selPay(2)">
|
||
<radio :checked="payMethod === 2"></radio>
|
||
<text>支付宝</text>
|
||
</label>
|
||
<label class="pay-item" @click="selPay(4)">
|
||
<radio :checked="payMethod === 4"></radio>
|
||
<text>余额</text>
|
||
</label>
|
||
</view>
|
||
|
||
<view class="actions">
|
||
<button class="primary" :disabled="submitting" @click="confirmSubscribe">确认并支付</button>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref } from 'vue'
|
||
import { onLoad } from '@dcloudio/uni-app'
|
||
import supaClient from '@/components/supadb/aksupainstance.uts'
|
||
import { goToLogin } from '@/utils/utils.uts'
|
||
|
||
const planId = ref<string>('')
|
||
const loading = ref<boolean>(true)
|
||
const plan = ref<UTSJSONObject | null>(null)
|
||
const payMethod = ref<number>(1)
|
||
const trialDays = ref<number>(0)
|
||
const submitting = ref<boolean>(false)
|
||
|
||
async function loadPlan(): Promise<void> {
|
||
try {
|
||
loading.value = true
|
||
const res = await supaClient
|
||
.from('ml_subscription_plans')
|
||
.select('*', {})
|
||
.eq('id', planId.value)
|
||
.single()
|
||
.execute()
|
||
if (res != null && res.error == null) {
|
||
if (Array.isArray(res.data)) {
|
||
plan.value = (res.data as Array<UTSJSONObject>)[0] ?? null
|
||
} else {
|
||
plan.value = res.data as UTSJSONObject
|
||
}
|
||
trialDays.value = plan.value != null ? (plan.value.getNumber('trial_days') ?? 0) : 0
|
||
} else {
|
||
plan.value = null
|
||
}
|
||
} catch (e) {
|
||
console.error('加载方案失败:', e)
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
onLoad((opts) => {
|
||
if (opts == null) return
|
||
const optObj = opts as UTSJSONObject
|
||
planId.value = optObj.getString('planId') ?? ''
|
||
loadPlan()
|
||
})
|
||
|
||
const selPay = (v: number) => { payMethod.value = v }
|
||
|
||
// 获取当前用户ID(按现有store实现替换)
|
||
const getCurrentUserId = (): string => {
|
||
try {
|
||
const session = supaClient.getSession()
|
||
return (session != null && session.user != null) ? (session.user.getString('id') ?? '') : ''
|
||
} catch (e) {
|
||
return ''
|
||
}
|
||
}
|
||
|
||
const confirmSubscribe = async () => {
|
||
if (plan.value == null) return
|
||
const userId = getCurrentUserId()
|
||
if (userId.length === 0) {
|
||
goToLogin(`/pages/mall/consumer/subscription/subscribe-checkout?planId=${planId.value}`)
|
||
return
|
||
}
|
||
|
||
submitting.value = true
|
||
try {
|
||
// 1) 创建订单或支付意图(此处简化为直接创建订阅记录)
|
||
const now = new Date()
|
||
const start = now.toISOString()
|
||
// 简单计算下个扣费日
|
||
let nextBilling: string | null = null
|
||
const billingPeriod = plan.value.getString('billing_period') ?? 'monthly'
|
||
if (billingPeriod === 'yearly') {
|
||
nextBilling = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate()).toISOString()
|
||
} else {
|
||
nextBilling = new Date(now.getFullYear(), now.getMonth() + 1, now.getDate()).toISOString()
|
||
}
|
||
|
||
const metadata = new UTSJSONObject()
|
||
metadata.set('pay_method', payMethod.value)
|
||
const body = new UTSJSONObject()
|
||
body.set('user_id', userId)
|
||
body.set('plan_id', plan.value.getString('id') ?? '')
|
||
body.set('status', 'active')
|
||
body.set('start_date', start)
|
||
body.set('end_date', null)
|
||
body.set('next_billing_date', nextBilling)
|
||
body.set('auto_renew', true)
|
||
body.set('metadata', metadata)
|
||
|
||
const ins = await supaClient
|
||
.from('ml_user_subscriptions')
|
||
.insert(body)
|
||
.execute()
|
||
if (ins != null && ins.error == null) {
|
||
uni.showToast({ title: '订阅成功', icon: 'success' })
|
||
setTimeout(() => {
|
||
uni.switchTab({ url: '/pages/main/profile' })
|
||
}, 600)
|
||
} else {
|
||
uni.showToast({ title: ins?.error?.message ?? '订阅失败', icon: 'none' })
|
||
}
|
||
} catch (e) {
|
||
console.error('订阅失败:', e)
|
||
uni.showToast({ title: '订阅失败', icon: 'none' })
|
||
} finally {
|
||
submitting.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.subscribe-checkout { padding: 12px; }
|
||
.header { margin-bottom: 8px; }
|
||
.title { font-size: 18px; font-weight: 700; }
|
||
.card { background: #fff; border-radius: 10px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
|
||
.row { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f0f0f0; }
|
||
.row:last-child { border-bottom: none; }
|
||
.label { color: #666; }
|
||
.value { color: #111; font-weight: 700; }
|
||
.section-title { margin-top: 12px; font-weight: 700; }
|
||
.pay-methods { display: flex; flex-direction: column; padding: 8px 0; }
|
||
.pay-item { display: flex; align-items: center; margin-bottom: 8px; }
|
||
.pay-item:last-child { margin-bottom: 0; }
|
||
.pay-icon { margin-right: 8px; }
|
||
.actions { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||
.primary { background: #3cc51f; color: #fff; border-radius: 6px; padding: 8px 12px; }
|
||
.loading, .empty { padding: 24px; text-align: center; color: #888; }
|
||
</style> |