web-app/src/modules/events/composables/useTicketPurchase.ts
padreug 5633aa154b Enhance PaymentService and useTicketPurchase composable for improved wallet handling
- Introduced asynchronous methods in PaymentService for retrieving user wallets and checking wallet balances, allowing for dual authentication detection.
- Updated getUserWalletsAsync, hasWalletWithBalanceAsync, and getWalletWithBalanceAsync methods to streamline wallet access and balance checks.
- Refactored useTicketPurchase composable to load user wallets asynchronously on component mount, improving user experience during ticket purchases.
- Enhanced error handling and logging for wallet loading and payment processes.

These changes improve the reliability and responsiveness of wallet interactions within the payment flow.
2025-09-07 00:19:43 +02:00

247 lines
No EOL
7.2 KiB
TypeScript

import { ref, computed, onUnmounted, onMounted } from 'vue'
import { purchaseTicket, checkPaymentStatus } from '@/lib/api/events'
import { useAuth } from '@/composables/useAuth'
import { toast } from 'vue-sonner'
import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
import type { PaymentService } from '@/core/services/PaymentService'
export function useTicketPurchase() {
const { isAuthenticated, currentUser } = useAuth()
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as PaymentService
// Async operations
const purchaseOperation = useAsyncOperation()
// State
const paymentHash = ref<string | null>(null)
const paymentRequest = ref<string | null>(null)
const qrCode = ref<string | null>(null)
const isPaymentPending = ref(false)
// Ticket QR code state
const ticketQRCode = ref<string | null>(null)
const purchasedTicketId = ref<string | null>(null)
const showTicketQR = ref(false)
// Wallet state (loaded asynchronously)
const userWallets = ref<any[]>([])
const hasWalletWithBalance = ref(false)
// Computed properties
const canPurchase = computed(() => isAuthenticated.value && currentUser.value)
const userDisplay = computed(() => {
if (!currentUser.value) return null
return {
name: currentUser.value.username || currentUser.value.id,
shortId: currentUser.value.id.slice(0, 8)
}
})
// Delegate to PaymentService for processing status
const isPayingWithWallet = computed(() => paymentService.isProcessingPayment.value) // computed ref
// Load wallets asynchronously
async function loadWallets() {
try {
const wallets = await paymentService.getUserWalletsAsync()
userWallets.value = wallets
hasWalletWithBalance.value = await paymentService.hasWalletWithBalanceAsync()
console.log('Loaded wallets for ticket purchase:', {
count: wallets.length,
hasBalance: hasWalletWithBalance.value
})
} catch (error) {
console.error('Failed to load wallets:', error)
userWallets.value = []
hasWalletWithBalance.value = false
}
}
// Load wallets on mount
onMounted(() => {
loadWallets()
})
// Generate QR code for Lightning payment - delegate to PaymentService
async function generateQRCode(bolt11: string) {
try {
qrCode.value = await paymentService.generateQRCode(bolt11)
} catch (err) {
console.error('Error generating QR code:', err)
// Note: error handling is now managed by the purchaseOperation
}
}
// Generate QR code for ticket - delegate to PaymentService
async function generateTicketQRCode(ticketId: string) {
try {
const ticketUrl = `ticket://${ticketId}`
const dataUrl = await paymentService.generateCustomQRCode(ticketUrl, {
width: 128,
margin: 2
})
ticketQRCode.value = dataUrl
return dataUrl
} catch (error) {
console.error('Error generating ticket QR code:', error)
return null
}
}
// Pay with wallet - delegate to PaymentService
async function payWithWallet(paymentRequest: string) {
try {
await paymentService.payWithWallet(paymentRequest, undefined, {
showToast: false // We'll handle success notifications in the ticket purchase flow
})
return true
} catch (error) {
console.error('Wallet payment failed:', error)
throw error
}
}
// Purchase ticket for event
async function purchaseTicketForEvent(eventId: string) {
if (!canPurchase.value) {
throw new Error('User must be authenticated to purchase tickets')
}
return await purchaseOperation.execute(async () => {
// Clear previous state
paymentHash.value = null
paymentRequest.value = null
qrCode.value = null
ticketQRCode.value = null
purchasedTicketId.value = null
showTicketQR.value = false
// Get the invoice
const invoice = await purchaseTicket(eventId)
paymentHash.value = invoice.payment_hash
paymentRequest.value = invoice.payment_request
// Generate QR code for payment
await generateQRCode(invoice.payment_request)
// Try to pay with wallet if available
if (hasWalletWithBalance.value) {
try {
await payWithWallet(invoice.payment_request)
// If wallet payment succeeds, proceed to check payment status
await startPaymentStatusCheck(eventId, invoice.payment_hash)
} catch (walletError) {
// If wallet payment fails, fall back to manual payment
console.log('Wallet payment failed, falling back to manual payment:', walletError)
await startPaymentStatusCheck(eventId, invoice.payment_hash)
}
} else {
// No wallet balance, proceed with manual payment
await startPaymentStatusCheck(eventId, invoice.payment_hash)
}
return invoice
}, {
errorMessage: 'Failed to purchase ticket'
})
}
// Start payment status check
async function startPaymentStatusCheck(eventId: string, hash: string) {
isPaymentPending.value = true
let checkInterval: number | null = null
const checkPayment = async () => {
try {
const result = await checkPaymentStatus(eventId, hash)
if (result.paid) {
isPaymentPending.value = false
if (checkInterval) {
clearInterval(checkInterval)
}
// Generate ticket QR code
if (result.ticket_id) {
purchasedTicketId.value = result.ticket_id
await generateTicketQRCode(result.ticket_id)
showTicketQR.value = true
}
toast.success('Ticket purchased successfully!')
}
} catch (err) {
console.error('Error checking payment status:', err)
}
}
// Check immediately
await checkPayment()
// Then check every 2 seconds
checkInterval = setInterval(checkPayment, 2000) as unknown as number
}
// Stop payment status check
function stopPaymentStatusCheck() {
isPaymentPending.value = false
}
// Reset payment state
function resetPaymentState() {
purchaseOperation.clear()
paymentHash.value = null
paymentRequest.value = null
qrCode.value = null
isPaymentPending.value = false
ticketQRCode.value = null
purchasedTicketId.value = null
showTicketQR.value = false
}
// Open Lightning wallet - delegate to PaymentService
function handleOpenLightningWallet() {
if (paymentRequest.value) {
paymentService.openExternalWallet(paymentRequest.value)
}
}
// Cleanup function
function cleanup() {
stopPaymentStatusCheck()
}
// Lifecycle
onUnmounted(() => {
cleanup()
})
return {
// State
isLoading: purchaseOperation.isLoading,
error: purchaseOperation.error,
paymentHash,
paymentRequest,
qrCode,
isPaymentPending,
isPayingWithWallet,
ticketQRCode,
purchasedTicketId,
showTicketQR,
// Computed
canPurchase,
userDisplay,
userWallets,
hasWalletWithBalance,
// Actions
purchaseTicketForEvent,
handleOpenLightningWallet,
resetPaymentState,
cleanup,
generateTicketQRCode,
loadWallets
}
}