- 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.
247 lines
No EOL
7.2 KiB
TypeScript
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
|
|
}
|
|
}
|