web-app/src/composables/useTicketPurchase.ts
padreug f7450627bc refactor: Revamp PurchaseTicketDialog and introduce useTicketPurchase composable
- Update PurchaseTicketDialog.vue to integrate authentication checks and enhance ticket purchasing flow with wallet support.
- Implement useTicketPurchase composable for managing ticket purchase logic, including payment handling and QR code generation.
- Improve user experience by displaying user information and wallet status during the ticket purchase process.
- Refactor API interactions in events.ts to streamline ticket purchasing and payment status checks.
2025-08-03 11:20:57 +02:00

220 lines
No EOL
6.1 KiB
TypeScript

import { ref, computed } from 'vue'
import { purchaseTicket, checkPaymentStatus, payInvoiceWithWallet } from '@/lib/api/events'
import { useAuth } from './useAuth'
import { toast } from 'vue-sonner'
interface Wallet {
id: string
user: string
name: string
adminkey: string
inkey: string
deleted: boolean
created_at: string
updated_at: string
currency?: string
balance_msat: number
extra?: {
icon: string
color: string
pinned: boolean
}
}
export function useTicketPurchase() {
const { isAuthenticated, currentUser } = useAuth()
const isLoading = ref(false)
const error = ref<string | null>(null)
const paymentHash = ref('')
const paymentRequest = ref('')
const qrCode = ref('')
const isPaymentPending = ref(false)
const paymentCheckInterval = ref<number | null>(null)
const isPayingWithWallet = ref(false)
const canPurchase = computed(() => {
return isAuthenticated.value && !isLoading.value
})
const userDisplay = computed(() => {
if (!currentUser.value) return null
return {
name: currentUser.value.username || currentUser.value.email || 'Anonymous',
id: currentUser.value.id,
shortId: currentUser.value.id.slice(0, 8) + '...' + currentUser.value.id.slice(-8)
}
})
const userWallets = computed(() => {
if (!currentUser.value) return [] as Wallet[]
return currentUser.value.wallets || []
})
const hasWalletWithBalance = computed(() => {
return userWallets.value.some((wallet: Wallet) => wallet.balance_msat > 0)
})
async function generateQRCode(bolt11: string) {
try {
const QRCode = await import('qrcode')
qrCode.value = await QRCode.toDataURL(`lightning:${bolt11}`)
} catch (err) {
console.error('Failed to generate QR code:', err)
}
}
async function payWithWallet(paymentRequest: string) {
if (!currentUser.value || !userWallets.value.length) {
throw new Error('No wallet available for payment')
}
// Find the first wallet with sufficient balance
const wallet = userWallets.value.find((w: Wallet) => w.balance_msat > 0)
if (!wallet) {
throw new Error('No wallet with sufficient balance found')
}
try {
isPayingWithWallet.value = true
const result = await payInvoiceWithWallet(paymentRequest, wallet.id, wallet.adminkey)
toast.success(`Payment successful! Fee: ${result.fee_msat} msat`)
return result
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to pay with wallet'
toast.error(message)
throw err
} finally {
isPayingWithWallet.value = false
}
}
async function purchaseTicketForEvent(eventId: string) {
if (!isAuthenticated.value) {
error.value = 'Please log in to purchase tickets'
toast.error('Please log in to purchase tickets')
return
}
try {
isLoading.value = true
error.value = ''
// Step 1: Get the invoice
const result = await purchaseTicket(eventId)
paymentHash.value = result.payment_hash
paymentRequest.value = result.payment_request
await generateQRCode(result.payment_request)
// Step 2: Try to pay with wallet if user has balance
if (hasWalletWithBalance.value) {
try {
await payWithWallet(result.payment_request)
// Payment successful, start monitoring for ticket confirmation
startPaymentStatusCheck(eventId, result.payment_hash)
return
} catch (walletPaymentError) {
console.log('Wallet payment failed, showing QR code for manual payment:', walletPaymentError)
// Fall back to QR code payment
startPaymentStatusCheck(eventId, result.payment_hash)
}
} else {
// No wallet balance, show QR code for manual payment
startPaymentStatusCheck(eventId, result.payment_hash)
}
toast.success('Payment request generated successfully')
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to generate payment request'
error.value = message
toast.error(message)
throw err
} finally {
isLoading.value = false
}
}
function startPaymentStatusCheck(eventId: string, paymentHash: string) {
isPaymentPending.value = true
// Clear any existing interval
if (paymentCheckInterval.value) {
clearInterval(paymentCheckInterval.value)
}
// Check payment status every 2 seconds
paymentCheckInterval.value = window.setInterval(async () => {
try {
const status = await checkPaymentStatus(eventId, paymentHash)
if (status.paid) {
// Payment successful
clearInterval(paymentCheckInterval.value!)
paymentCheckInterval.value = null
isPaymentPending.value = false
toast.success('Payment successful! Your ticket has been purchased.')
// Reset payment state
resetPaymentState()
return { success: true, ticketId: status.ticket_id }
}
} catch (err) {
console.error('Error checking payment status:', err)
// Don't show error to user for status checks, just log it
}
}, 2000)
}
function stopPaymentStatusCheck() {
if (paymentCheckInterval.value) {
clearInterval(paymentCheckInterval.value)
paymentCheckInterval.value = null
}
isPaymentPending.value = false
}
function resetPaymentState() {
paymentHash.value = ''
paymentRequest.value = ''
qrCode.value = ''
error.value = ''
stopPaymentStatusCheck()
}
function handleOpenLightningWallet() {
if (paymentRequest.value) {
window.location.href = `lightning:${paymentRequest.value}`
}
}
// Cleanup on unmount
function cleanup() {
stopPaymentStatusCheck()
}
return {
// State
isLoading,
error,
paymentHash,
paymentRequest,
qrCode,
isPaymentPending,
isPayingWithWallet,
canPurchase,
userDisplay,
userWallets,
hasWalletWithBalance,
// Actions
purchaseTicketForEvent,
payWithWallet,
handleOpenLightningWallet,
resetPaymentState,
cleanup,
}
}