Enhance payment processing and rental features

- Updated the BTCPay service to support internal Lightning invoices with private route hints, improving payment routing for users with private channels.
- Added reconciliation methods for pending rents and subscriptions to ensure missed payments are processed on startup.
- Enhanced the rental and subscription services to handle payments in satoshis, aligning with Lightning Network standards.
- Improved the rental modal and content detail components to display rental status and pricing more clearly, including a countdown for rental expiration.
- Refactored various components to streamline user experience and ensure accurate rental access checks.
This commit is contained in:
Dorian
2026-02-12 23:24:25 +00:00
parent cdd24a5def
commit 0da83f461c
39 changed files with 1182 additions and 270 deletions

View File

@@ -30,18 +30,18 @@
: 'text-white/60 hover:text-white'
]"
>
Monthly
1 Month
</button>
<button
@click="period = 'annual'"
@click="period = 'yearly'"
:class="[
'px-6 py-2 rounded-lg font-medium transition-all flex items-center gap-2',
period === 'annual'
period === 'yearly'
? 'bg-white text-black'
: 'text-white/60 hover:text-white'
]"
>
Annual
1 Year
<span class="text-xs bg-orange-500 text-white px-2 py-0.5 rounded-full">Save 17%</span>
</button>
</div>
@@ -64,12 +64,13 @@
@click="selectedTier = tier.tier"
>
<h3 class="text-xl font-bold text-white mb-2">{{ tier.name }}</h3>
<div class="mb-4">
<div class="mb-4 flex items-baseline gap-1">
<svg class="w-5 h-5 text-yellow-500 self-center" viewBox="0 0 24 24" fill="currentColor"><path d="M13 3l-2 7h5l-6 11 2-7H7l6-11z"/></svg>
<span class="text-3xl font-bold text-white">
${{ period === 'monthly' ? tier.monthlyPrice : tier.annualPrice }}
{{ (period === 'monthly' ? tier.monthlyPrice : tier.annualPrice).toLocaleString() }}
</span>
<span class="text-white/60 text-sm">
/{{ period === 'monthly' ? 'month' : 'year' }}
sats / {{ period === 'monthly' ? '1 month' : '1 year' }}
</span>
</div>
@@ -91,14 +92,14 @@
class="hero-play-button w-full flex items-center justify-center"
>
<svg v-if="!isLoading" class="w-5 h-5 mr-2" viewBox="0 0 24 24" fill="currentColor">
<path d="M13 10h-3V7H7v3H4v3h3v3h3v-3h3v-3zm8-6a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2V4zm-2 0l.01 14H5V4h14z"/>
<path d="M13 3l-2 7h5l-6 11 2-7H7l6-11z"/>
</svg>
<span v-if="!isLoading">Pay with Lightning ${{ selectedPrice }}</span>
<span v-if="!isLoading">Pay with Lightning {{ Number(selectedPrice).toLocaleString() }} sats</span>
<span v-else>Creating invoice...</span>
</button>
<p class="text-center text-xs text-white/40 mt-4">
Pay once per period. Renew manually when your plan expires.
One-time payment. Renew manually when your plan expires.
</p>
</template>
@@ -107,7 +108,7 @@
<div class="text-center">
<h2 class="text-2xl font-bold text-white mb-2">Pay with Lightning</h2>
<p class="text-white/60 text-sm mb-1">
{{ selectedTierName }} {{ period === 'monthly' ? 'Monthly' : 'Annual' }}
{{ selectedTierName }} {{ period === 'monthly' ? '1 Month' : '1 Year' }}
</p>
<p class="text-white/40 text-xs mb-6">
Scan the QR code or copy the invoice to pay
@@ -123,10 +124,10 @@
<!-- Amount -->
<div class="mb-4">
<div class="text-lg font-bold text-white">
{{ formatSats(invoiceData?.sourceAmount?.amount) }} sats
<div class="text-lg font-bold text-white flex items-center justify-center gap-1">
<svg class="w-5 h-5 text-yellow-500" viewBox="0 0 24 24" fill="currentColor"><path d="M13 3l-2 7h5l-6 11 2-7H7l6-11z"/></svg>
{{ displaySats }} sats
</div>
<div class="text-sm text-white/60"> ${{ selectedPrice }} USD</div>
</div>
<!-- Expiration Countdown -->
@@ -229,7 +230,7 @@ const emit = defineEmits<Emits>()
type PaymentState = 'select' | 'invoice' | 'success' | 'expired'
const paymentState = ref<PaymentState>('select')
const period = ref<'monthly' | 'annual'>('monthly')
const period = ref<'monthly' | 'yearly'>('monthly')
const selectedTier = ref<string>('film-buff')
const tiers = ref<any[]>([])
const isLoading = ref(false)
@@ -259,6 +260,17 @@ const selectedPrice = computed(() => {
return period.value === 'monthly' ? tier.monthlyPrice : tier.annualPrice
})
/**
* Display sats — prefer invoice source amount (from BTCPay), fall back to tier price.
*/
const displaySats = computed(() => {
const sourceAmount = invoiceData.value?.sourceAmount?.amount
if (sourceAmount) {
return formatSats(sourceAmount)
}
return Number(selectedPrice.value).toLocaleString()
})
onMounted(async () => {
tiers.value = await subscriptionService.getSubscriptionTiers()
})
@@ -372,7 +384,7 @@ async function handleSubscribe() {
// Real API call — create Lightning subscription invoice
const result = await subscriptionService.createLightningSubscription({
type: selectedTier.value as any,
period: period.value,
period: period.value as 'monthly' | 'yearly',
})
invoiceData.value = result