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.
This commit is contained in:
padreug 2025-09-07 00:19:43 +02:00
parent 5a899d1501
commit 5633aa154b
3 changed files with 123 additions and 16 deletions

View file

@ -52,21 +52,81 @@ export class PaymentService extends BaseService {
} }
/** /**
* Get user wallets from authenticated user * Get global auth composable
*/
private async getGlobalAuth() {
try {
// Use async dynamic import to avoid circular dependencies
const { auth } = await import('@/composables/useAuth')
return auth
} catch (error) {
this.debug('Could not access global auth:', error)
return null
}
}
/**
* Get user wallets from authenticated user (using dual auth detection)
*/
async getUserWalletsAsync() {
// Check both injected auth service AND global auth composable
const hasAuthService = this.authService?.user?.value?.wallets
const globalAuth = await this.getGlobalAuth()
const hasGlobalAuth = globalAuth?.currentUser?.value?.wallets
const wallets = hasAuthService || hasGlobalAuth || []
this.debug('Getting user wallets:', {
hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth,
walletsCount: Array.isArray(wallets) ? wallets.length : 0,
walletsSource: hasAuthService ? 'authService' : (hasGlobalAuth ? 'globalAuth' : 'none')
})
return wallets
}
/**
* Get user wallets from authenticated user (synchronous fallback)
*/ */
get userWallets() { get userWallets() {
// Fallback to just auth service for synchronous access
return this.authService?.user?.value?.wallets || [] return this.authService?.user?.value?.wallets || []
} }
/** /**
* Check if user has any wallet with balance * Check if user has any wallet with balance (async version)
*/
async hasWalletWithBalanceAsync(): Promise<boolean> {
const wallets = await this.getUserWalletsAsync()
return wallets.some((wallet: any) => wallet.balance_msat > 0)
}
/**
* Check if user has any wallet with balance (synchronous fallback)
*/ */
get hasWalletWithBalance(): boolean { get hasWalletWithBalance(): boolean {
return this.userWallets.some((wallet: any) => wallet.balance_msat > 0) return this.userWallets.some((wallet: any) => wallet.balance_msat > 0)
} }
/** /**
* Find wallet with sufficient balance for payment * Find wallet with sufficient balance for payment (async version)
*/
async getWalletWithBalanceAsync(requiredAmountSats?: number): Promise<any | null> {
const wallets = await this.getUserWalletsAsync()
if (!wallets.length) return null
if (requiredAmountSats) {
// Convert sats to msat for comparison
const requiredMsat = requiredAmountSats * 1000
return wallets.find((wallet: any) => wallet.balance_msat >= requiredMsat)
}
return wallets.find((wallet: any) => wallet.balance_msat > 0)
}
/**
* Find wallet with sufficient balance for payment (synchronous fallback)
*/ */
getWalletWithBalance(requiredAmountSats?: number): any | null { getWalletWithBalance(requiredAmountSats?: number): any | null {
const wallets = this.userWallets const wallets = this.userWallets
@ -137,14 +197,26 @@ export class PaymentService extends BaseService {
requiredAmountSats?: number, requiredAmountSats?: number,
options: PaymentOptions = {} options: PaymentOptions = {}
): Promise<PaymentResult> { ): Promise<PaymentResult> {
// Check authentication // Check authentication using dual auth detection
if (!this.authService?.isAuthenticated?.value || !this.authService?.user?.value) { const hasAuthService = this.authService?.isAuthenticated?.value && this.authService?.user?.value
const globalAuth = await this.getGlobalAuth()
const hasGlobalAuth = globalAuth?.isAuthenticated?.value && globalAuth?.currentUser?.value
if (!hasAuthService && !hasGlobalAuth) {
this.debug('Payment failed - user not authenticated:', {
hasAuthService: !!hasAuthService,
hasGlobalAuth: !!hasGlobalAuth
})
throw new Error('User must be authenticated to pay with wallet') throw new Error('User must be authenticated to pay with wallet')
} }
// Find suitable wallet // Find suitable wallet using async version for proper dual auth detection
const wallet = this.getWalletWithBalance(requiredAmountSats) const wallet = await this.getWalletWithBalanceAsync(requiredAmountSats)
if (!wallet) { if (!wallet) {
this.debug('No wallet with sufficient balance found:', {
requiredAmountSats,
walletsAvailable: (await this.getUserWalletsAsync()).length
})
throw new Error('No wallet with sufficient balance found') throw new Error('No wallet with sufficient balance found')
} }
@ -235,8 +307,8 @@ export class PaymentService extends BaseService {
return null return null
} }
// Try wallet payment first if user has balance // Try wallet payment first if user has balance (use async check for proper dual auth detection)
if (this.hasWalletWithBalance) { if (await this.hasWalletWithBalanceAsync()) {
try { try {
return await this.payWithWallet( return await this.payWithWallet(
paymentRequest, paymentRequest,
@ -247,6 +319,8 @@ export class PaymentService extends BaseService {
this.debug('Wallet payment failed, offering external wallet option:', error) this.debug('Wallet payment failed, offering external wallet option:', error)
// Don't throw here, continue to external wallet option // Don't throw here, continue to external wallet option
} }
} else {
this.debug('No wallet with balance available, skipping wallet payment')
} }
// Fallback to external wallet // Fallback to external wallet

View file

@ -1,6 +1,6 @@
<!-- eslint-disable vue/multi-word-component-names --> <!-- eslint-disable vue/multi-word-component-names -->
<script setup lang="ts"> <script setup lang="ts">
import { onUnmounted } from 'vue' import { onUnmounted, watch } from 'vue'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
@ -44,7 +44,8 @@ const {
cleanup, cleanup,
ticketQRCode, ticketQRCode,
purchasedTicketId, purchasedTicketId,
showTicketQR showTicketQR,
loadWallets
} = useTicketPurchase() } = useTicketPurchase()
async function handlePurchase() { async function handlePurchase() {
@ -62,6 +63,13 @@ function handleClose() {
resetPaymentState() resetPaymentState()
} }
// Reload wallets when dialog opens
watch(() => props.isOpen, async (newVal) => {
if (newVal && isAuthenticated.value) {
await loadWallets()
}
})
// Cleanup on unmount // Cleanup on unmount
onUnmounted(() => { onUnmounted(() => {
cleanup() cleanup()

View file

@ -1,4 +1,4 @@
import { ref, computed, onUnmounted } from 'vue' import { ref, computed, onUnmounted, onMounted } from 'vue'
import { purchaseTicket, checkPaymentStatus } from '@/lib/api/events' import { purchaseTicket, checkPaymentStatus } from '@/lib/api/events'
import { useAuth } from '@/composables/useAuth' import { useAuth } from '@/composables/useAuth'
import { toast } from 'vue-sonner' import { toast } from 'vue-sonner'
@ -23,6 +23,10 @@ export function useTicketPurchase() {
const ticketQRCode = ref<string | null>(null) const ticketQRCode = ref<string | null>(null)
const purchasedTicketId = ref<string | null>(null) const purchasedTicketId = ref<string | null>(null)
const showTicketQR = ref(false) const showTicketQR = ref(false)
// Wallet state (loaded asynchronously)
const userWallets = ref<any[]>([])
const hasWalletWithBalance = ref(false)
// Computed properties // Computed properties
const canPurchase = computed(() => isAuthenticated.value && currentUser.value) const canPurchase = computed(() => isAuthenticated.value && currentUser.value)
@ -34,10 +38,30 @@ export function useTicketPurchase() {
} }
}) })
// Delegate to PaymentService // Delegate to PaymentService for processing status
const userWallets = computed(() => paymentService.userWallets) // getter method
const hasWalletWithBalance = computed(() => paymentService.hasWalletWithBalance) // getter method
const isPayingWithWallet = computed(() => paymentService.isProcessingPayment.value) // computed ref 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 // Generate QR code for Lightning payment - delegate to PaymentService
async function generateQRCode(bolt11: string) { async function generateQRCode(bolt11: string) {
@ -217,6 +241,7 @@ export function useTicketPurchase() {
handleOpenLightningWallet, handleOpenLightningWallet,
resetPaymentState, resetPaymentState,
cleanup, cleanup,
generateTicketQRCode generateTicketQRCode,
loadWallets
} }
} }