Add PaymentService integration to enhance ticket purchasing and lightning payment functionality
- Introduce PAYMENT_SERVICE token in di-container for dependency injection. - Update base module to register and initialize PaymentService, ensuring it is available for use. - Refactor useTicketPurchase and useLightningPayment composables to utilize PaymentService for wallet management, payment processing, and QR code generation. - Delegate payment handling and error management to PaymentService, streamlining the payment workflow and improving user experience.
This commit is contained in:
parent
adf32c0dca
commit
0bced11623
5 changed files with 372 additions and 137 deletions
|
|
@ -114,6 +114,9 @@ export const SERVICE_TOKENS = {
|
||||||
// Auth services
|
// Auth services
|
||||||
AUTH_SERVICE: Symbol('authService'),
|
AUTH_SERVICE: Symbol('authService'),
|
||||||
|
|
||||||
|
// Payment services
|
||||||
|
PAYMENT_SERVICE: Symbol('paymentService'),
|
||||||
|
|
||||||
// Market services
|
// Market services
|
||||||
MARKET_STORE: Symbol('marketStore'),
|
MARKET_STORE: Symbol('marketStore'),
|
||||||
PAYMENT_MONITOR: Symbol('paymentMonitor'),
|
PAYMENT_MONITOR: Symbol('paymentMonitor'),
|
||||||
|
|
|
||||||
280
src/core/services/PaymentService.ts
Normal file
280
src/core/services/PaymentService.ts
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { BaseService } from '@/core/base/BaseService'
|
||||||
|
import { payInvoiceWithWallet } from '@/lib/api/events'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
|
export interface PaymentResult {
|
||||||
|
payment_hash: string
|
||||||
|
fee_msat: number
|
||||||
|
preimage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QRCodeOptions {
|
||||||
|
width?: number
|
||||||
|
margin?: number
|
||||||
|
darkColor?: string
|
||||||
|
lightColor?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PaymentOptions {
|
||||||
|
successMessage?: string
|
||||||
|
errorMessage?: string
|
||||||
|
showToast?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Centralized Payment Service
|
||||||
|
* Handles Lightning payments, QR code generation, and wallet management
|
||||||
|
*/
|
||||||
|
export class PaymentService extends BaseService {
|
||||||
|
// Service metadata
|
||||||
|
protected readonly metadata = {
|
||||||
|
name: 'PaymentService',
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: ['AuthService']
|
||||||
|
}
|
||||||
|
|
||||||
|
// Payment state
|
||||||
|
private _isProcessingPayment = ref(false)
|
||||||
|
private _paymentError = ref<string | null>(null)
|
||||||
|
|
||||||
|
// Public reactive state
|
||||||
|
public readonly isProcessingPayment = computed(() => this._isProcessingPayment.value)
|
||||||
|
public readonly paymentError = computed(() => this._paymentError.value)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service-specific initialization (called by BaseService)
|
||||||
|
*/
|
||||||
|
protected async onInitialize(): Promise<void> {
|
||||||
|
this.debug('PaymentService initialized')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user wallets from authenticated user
|
||||||
|
*/
|
||||||
|
get userWallets() {
|
||||||
|
return this.authService?.user?.value?.wallets || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if user has any wallet with balance
|
||||||
|
*/
|
||||||
|
get hasWalletWithBalance(): boolean {
|
||||||
|
return this.userWallets.some((wallet: any) => wallet.balance_msat > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find wallet with sufficient balance for payment
|
||||||
|
*/
|
||||||
|
getWalletWithBalance(requiredAmountSats?: number): any | null {
|
||||||
|
const wallets = this.userWallets
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code for Lightning payment request
|
||||||
|
*/
|
||||||
|
async generateQRCode(
|
||||||
|
paymentRequest: string,
|
||||||
|
options: QRCodeOptions = {}
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const qrcode = await import('qrcode')
|
||||||
|
const dataUrl = await qrcode.toDataURL(paymentRequest, {
|
||||||
|
width: options.width || 256,
|
||||||
|
margin: options.margin || 2,
|
||||||
|
color: {
|
||||||
|
dark: options.darkColor || '#000000',
|
||||||
|
light: options.lightColor || '#FFFFFF'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dataUrl
|
||||||
|
} catch (error) {
|
||||||
|
const err = this.handleError(error, 'generateQRCode')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code for ticket or other custom data
|
||||||
|
*/
|
||||||
|
async generateCustomQRCode(
|
||||||
|
data: string,
|
||||||
|
options: QRCodeOptions = {}
|
||||||
|
): Promise<string> {
|
||||||
|
try {
|
||||||
|
const qrcode = await import('qrcode')
|
||||||
|
const dataUrl = await qrcode.toDataURL(data, {
|
||||||
|
width: options.width || 128,
|
||||||
|
margin: options.margin || 2,
|
||||||
|
color: {
|
||||||
|
dark: options.darkColor || '#000000',
|
||||||
|
light: options.lightColor || '#FFFFFF'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return dataUrl
|
||||||
|
} catch (error) {
|
||||||
|
const err = this.handleError(error, 'generateCustomQRCode')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pay Lightning invoice with user's wallet
|
||||||
|
*/
|
||||||
|
async payInvoiceWithUserWallet(
|
||||||
|
paymentRequest: string,
|
||||||
|
requiredAmountSats?: number,
|
||||||
|
options: PaymentOptions = {}
|
||||||
|
): Promise<PaymentResult> {
|
||||||
|
// Check authentication
|
||||||
|
if (!this.authService?.isAuthenticated?.value || !this.authService?.user?.value) {
|
||||||
|
throw new Error('User must be authenticated to pay with wallet')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find suitable wallet
|
||||||
|
const wallet = this.getWalletWithBalance(requiredAmountSats)
|
||||||
|
if (!wallet) {
|
||||||
|
throw new Error('No wallet with sufficient balance found')
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this._isProcessingPayment.value = true
|
||||||
|
this._paymentError.value = null
|
||||||
|
|
||||||
|
this.debug(`Paying invoice with wallet: ${wallet.id.slice(0, 8)}`)
|
||||||
|
|
||||||
|
// Make payment
|
||||||
|
const paymentResult = await payInvoiceWithWallet(
|
||||||
|
paymentRequest,
|
||||||
|
wallet.id,
|
||||||
|
wallet.adminkey
|
||||||
|
)
|
||||||
|
|
||||||
|
this.debug('Payment successful', {
|
||||||
|
paymentHash: paymentResult.payment_hash,
|
||||||
|
feeMsat: paymentResult.fee_msat
|
||||||
|
})
|
||||||
|
|
||||||
|
// Show success notification if enabled
|
||||||
|
if (options.showToast !== false) {
|
||||||
|
const message = options.successMessage || 'Payment successful!'
|
||||||
|
toast.success(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentResult
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Payment failed'
|
||||||
|
this._paymentError.value = errorMessage
|
||||||
|
|
||||||
|
this.debug('Payment failed', error)
|
||||||
|
|
||||||
|
// Show error notification if enabled
|
||||||
|
if (options.showToast !== false) {
|
||||||
|
const message = options.errorMessage || errorMessage
|
||||||
|
toast.error('Payment failed', {
|
||||||
|
description: message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error
|
||||||
|
} finally {
|
||||||
|
this._isProcessingPayment.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open external Lightning wallet
|
||||||
|
*/
|
||||||
|
openExternalLightningWallet(paymentRequest: string): void {
|
||||||
|
if (!paymentRequest) {
|
||||||
|
toast.error('No payment request available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const lightningUrl = `lightning:${paymentRequest}`
|
||||||
|
window.open(lightningUrl, '_blank')
|
||||||
|
|
||||||
|
toast.info('Opening Lightning wallet...', {
|
||||||
|
description: 'If no wallet opens, copy the payment request manually'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Failed to open lightning: URL:', error)
|
||||||
|
|
||||||
|
// Fallback: copy to clipboard
|
||||||
|
navigator.clipboard?.writeText(paymentRequest).then(() => {
|
||||||
|
toast.success('Payment request copied to clipboard')
|
||||||
|
}).catch(() => {
|
||||||
|
toast.info('Please copy the payment request manually')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle payment with automatic fallback
|
||||||
|
* Tries wallet payment first, falls back to external wallet
|
||||||
|
*/
|
||||||
|
async handlePaymentWithFallback(
|
||||||
|
paymentRequest: string,
|
||||||
|
requiredAmountSats?: number,
|
||||||
|
options: PaymentOptions = {}
|
||||||
|
): Promise<PaymentResult | null> {
|
||||||
|
if (!paymentRequest) {
|
||||||
|
toast.error('No payment request available')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try wallet payment first if user has balance
|
||||||
|
if (this.hasWalletWithBalance) {
|
||||||
|
try {
|
||||||
|
return await this.payInvoiceWithUserWallet(
|
||||||
|
paymentRequest,
|
||||||
|
requiredAmountSats,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
this.debug('Wallet payment failed, offering external wallet option:', error)
|
||||||
|
// Don't throw here, continue to external wallet option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to external wallet
|
||||||
|
this.openExternalLightningWallet(paymentRequest)
|
||||||
|
return null // External wallet payment status unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear payment error state
|
||||||
|
*/
|
||||||
|
clearPaymentError(): void {
|
||||||
|
this._paymentError.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset payment state
|
||||||
|
*/
|
||||||
|
resetPaymentState(): void {
|
||||||
|
this._isProcessingPayment.value = false
|
||||||
|
this._paymentError.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup when service is disposed (called by BaseService)
|
||||||
|
*/
|
||||||
|
protected async onDispose(): Promise<void> {
|
||||||
|
this.resetPaymentState()
|
||||||
|
this.debug('PaymentService disposed')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export singleton instance
|
||||||
|
export const paymentService = new PaymentService()
|
||||||
|
|
@ -10,6 +10,9 @@ import { auth } from './auth/auth-service'
|
||||||
// Import PWA services
|
// Import PWA services
|
||||||
import { pwaService } from './pwa/pwa-service'
|
import { pwaService } from './pwa/pwa-service'
|
||||||
|
|
||||||
|
// Import payment service
|
||||||
|
import { paymentService } from '@/core/services/PaymentService'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base Module Plugin
|
* Base Module Plugin
|
||||||
* Provides core infrastructure: Nostr, Auth, PWA, and UI components
|
* Provides core infrastructure: Nostr, Auth, PWA, and UI components
|
||||||
|
|
@ -28,6 +31,9 @@ export const baseModule: ModulePlugin = {
|
||||||
// Register auth service
|
// Register auth service
|
||||||
container.provide(SERVICE_TOKENS.AUTH_SERVICE, auth)
|
container.provide(SERVICE_TOKENS.AUTH_SERVICE, auth)
|
||||||
|
|
||||||
|
// Register payment service
|
||||||
|
container.provide(SERVICE_TOKENS.PAYMENT_SERVICE, paymentService)
|
||||||
|
|
||||||
// Register PWA service
|
// Register PWA service
|
||||||
container.provide('pwaService', pwaService)
|
container.provide('pwaService', pwaService)
|
||||||
|
|
||||||
|
|
@ -37,6 +43,10 @@ export const baseModule: ModulePlugin = {
|
||||||
waitForDependencies: false, // Auth has no dependencies
|
waitForDependencies: false, // Auth has no dependencies
|
||||||
maxRetries: 1
|
maxRetries: 1
|
||||||
})
|
})
|
||||||
|
await paymentService.initialize({
|
||||||
|
waitForDependencies: true, // PaymentService depends on AuthService
|
||||||
|
maxRetries: 3
|
||||||
|
})
|
||||||
|
|
||||||
console.log('✅ Base module installed successfully')
|
console.log('✅ Base module installed successfully')
|
||||||
},
|
},
|
||||||
|
|
@ -55,6 +65,7 @@ export const baseModule: ModulePlugin = {
|
||||||
relayHub,
|
relayHub,
|
||||||
nostrclientHub,
|
nostrclientHub,
|
||||||
auth,
|
auth,
|
||||||
|
paymentService,
|
||||||
pwaService
|
pwaService
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
import { ref, computed, onUnmounted } from 'vue'
|
import { ref, computed, onUnmounted } from 'vue'
|
||||||
import { purchaseTicket, checkPaymentStatus, payInvoiceWithWallet } 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'
|
||||||
import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
|
import { useAsyncOperation } from '@/core/composables/useAsyncOperation'
|
||||||
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
|
import type { PaymentService } from '@/core/services/PaymentService'
|
||||||
|
|
||||||
export function useTicketPurchase() {
|
export function useTicketPurchase() {
|
||||||
const { isAuthenticated, currentUser } = useAuth()
|
const { isAuthenticated, currentUser } = useAuth()
|
||||||
|
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as PaymentService
|
||||||
|
|
||||||
// Async operations
|
// Async operations
|
||||||
const purchaseOperation = useAsyncOperation()
|
const purchaseOperation = useAsyncOperation()
|
||||||
|
|
@ -15,7 +18,6 @@ export function useTicketPurchase() {
|
||||||
const paymentRequest = ref<string | null>(null)
|
const paymentRequest = ref<string | null>(null)
|
||||||
const qrCode = ref<string | null>(null)
|
const qrCode = ref<string | null>(null)
|
||||||
const isPaymentPending = ref(false)
|
const isPaymentPending = ref(false)
|
||||||
const isPayingWithWallet = ref(false)
|
|
||||||
|
|
||||||
// Ticket QR code state
|
// Ticket QR code state
|
||||||
const ticketQRCode = ref<string | null>(null)
|
const ticketQRCode = ref<string | null>(null)
|
||||||
|
|
@ -32,42 +34,28 @@ export function useTicketPurchase() {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const userWallets = computed(() => currentUser.value?.wallets || [])
|
// Delegate to PaymentService
|
||||||
const hasWalletWithBalance = computed(() =>
|
const userWallets = computed(() => paymentService.userWallets)
|
||||||
userWallets.value.some((wallet: any) => wallet.balance_msat > 0)
|
const hasWalletWithBalance = computed(() => paymentService.hasWalletWithBalance)
|
||||||
)
|
const isPayingWithWallet = computed(() => paymentService.isProcessingPayment)
|
||||||
|
|
||||||
// Generate QR code for Lightning payment
|
// Generate QR code for Lightning payment - delegate to PaymentService
|
||||||
async function generateQRCode(bolt11: string) {
|
async function generateQRCode(bolt11: string) {
|
||||||
try {
|
try {
|
||||||
const qrcode = await import('qrcode')
|
qrCode.value = await paymentService.generateQRCode(bolt11)
|
||||||
const dataUrl = await qrcode.toDataURL(bolt11, {
|
|
||||||
width: 256,
|
|
||||||
margin: 2,
|
|
||||||
color: {
|
|
||||||
dark: '#000000',
|
|
||||||
light: '#FFFFFF'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
qrCode.value = dataUrl
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error generating QR code:', err)
|
console.error('Error generating QR code:', err)
|
||||||
// Note: error handling is now managed by the purchaseOperation
|
// Note: error handling is now managed by the purchaseOperation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate QR code for ticket
|
// Generate QR code for ticket - delegate to PaymentService
|
||||||
async function generateTicketQRCode(ticketId: string) {
|
async function generateTicketQRCode(ticketId: string) {
|
||||||
try {
|
try {
|
||||||
const qrcode = await import('qrcode')
|
|
||||||
const ticketUrl = `ticket://${ticketId}`
|
const ticketUrl = `ticket://${ticketId}`
|
||||||
const dataUrl = await qrcode.toDataURL(ticketUrl, {
|
const dataUrl = await paymentService.generateCustomQRCode(ticketUrl, {
|
||||||
width: 128,
|
width: 128,
|
||||||
margin: 2,
|
margin: 2
|
||||||
color: {
|
|
||||||
dark: '#000000',
|
|
||||||
light: '#FFFFFF'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
ticketQRCode.value = dataUrl
|
ticketQRCode.value = dataUrl
|
||||||
return dataUrl
|
return dataUrl
|
||||||
|
|
@ -77,16 +65,12 @@ export function useTicketPurchase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pay with wallet
|
// Pay with wallet - delegate to PaymentService
|
||||||
async function payWithWallet(paymentRequest: string) {
|
async function payWithWallet(paymentRequest: string) {
|
||||||
const walletWithBalance = userWallets.value.find((wallet: any) => wallet.balance_msat > 0)
|
|
||||||
|
|
||||||
if (!walletWithBalance) {
|
|
||||||
throw new Error('No wallet with sufficient balance found')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await payInvoiceWithWallet(paymentRequest, walletWithBalance.id, walletWithBalance.adminkey)
|
await paymentService.payInvoiceWithUserWallet(paymentRequest, undefined, {
|
||||||
|
showToast: false // We'll handle success notifications in the ticket purchase flow
|
||||||
|
})
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Wallet payment failed:', error)
|
console.error('Wallet payment failed:', error)
|
||||||
|
|
@ -119,7 +103,6 @@ export function useTicketPurchase() {
|
||||||
|
|
||||||
// Try to pay with wallet if available
|
// Try to pay with wallet if available
|
||||||
if (hasWalletWithBalance.value) {
|
if (hasWalletWithBalance.value) {
|
||||||
isPayingWithWallet.value = true
|
|
||||||
try {
|
try {
|
||||||
await payWithWallet(invoice.payment_request)
|
await payWithWallet(invoice.payment_request)
|
||||||
// If wallet payment succeeds, proceed to check payment status
|
// If wallet payment succeeds, proceed to check payment status
|
||||||
|
|
@ -127,7 +110,6 @@ export function useTicketPurchase() {
|
||||||
} catch (walletError) {
|
} catch (walletError) {
|
||||||
// If wallet payment fails, fall back to manual payment
|
// If wallet payment fails, fall back to manual payment
|
||||||
console.log('Wallet payment failed, falling back to manual payment:', walletError)
|
console.log('Wallet payment failed, falling back to manual payment:', walletError)
|
||||||
isPayingWithWallet.value = false
|
|
||||||
await startPaymentStatusCheck(eventId, invoice.payment_hash)
|
await startPaymentStatusCheck(eventId, invoice.payment_hash)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -189,16 +171,15 @@ export function useTicketPurchase() {
|
||||||
paymentRequest.value = null
|
paymentRequest.value = null
|
||||||
qrCode.value = null
|
qrCode.value = null
|
||||||
isPaymentPending.value = false
|
isPaymentPending.value = false
|
||||||
isPayingWithWallet.value = false
|
|
||||||
ticketQRCode.value = null
|
ticketQRCode.value = null
|
||||||
purchasedTicketId.value = null
|
purchasedTicketId.value = null
|
||||||
showTicketQR.value = false
|
showTicketQR.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open Lightning wallet
|
// Open Lightning wallet - delegate to PaymentService
|
||||||
function handleOpenLightningWallet() {
|
function handleOpenLightningWallet() {
|
||||||
if (paymentRequest.value) {
|
if (paymentRequest.value) {
|
||||||
window.open(`lightning:${paymentRequest.value}`, '_blank')
|
paymentService.openExternalLightningWallet(paymentRequest.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,36 @@
|
||||||
import { ref, computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAuth } from '@/composables/useAuth'
|
|
||||||
import { useMarketStore } from '../stores/market'
|
import { useMarketStore } from '../stores/market'
|
||||||
import { payInvoiceWithWallet } from '@/lib/api/events'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import { toast } from 'vue-sonner'
|
import type { PaymentService } from '@/core/services/PaymentService'
|
||||||
|
|
||||||
export function useLightningPayment() {
|
export function useLightningPayment() {
|
||||||
const { isAuthenticated, currentUser } = useAuth()
|
const paymentService = injectService(SERVICE_TOKENS.PAYMENT_SERVICE) as PaymentService
|
||||||
const marketStore = useMarketStore()
|
const marketStore = useMarketStore()
|
||||||
|
|
||||||
// State
|
|
||||||
const isPayingWithWallet = ref(false)
|
|
||||||
const paymentError = ref<string | null>(null)
|
|
||||||
|
|
||||||
// Computed properties
|
// Computed properties - delegate to PaymentService
|
||||||
const userWallets = computed(() => currentUser.value?.wallets || [])
|
const userWallets = computed(() => paymentService.userWallets)
|
||||||
const hasWalletWithBalance = computed(() =>
|
const hasWalletWithBalance = computed(() => paymentService.hasWalletWithBalance)
|
||||||
userWallets.value.some((wallet: any) => wallet.balance_msat > 0)
|
const isPayingWithWallet = computed(() => paymentService.isProcessingPayment)
|
||||||
)
|
const paymentError = computed(() => paymentService.paymentError)
|
||||||
|
|
||||||
// Get wallet with sufficient balance
|
// Get wallet with sufficient balance - delegate to PaymentService
|
||||||
const getWalletWithBalance = (requiredAmount?: number) => {
|
const getWalletWithBalance = (requiredAmountSats?: number) => {
|
||||||
const wallets = userWallets.value
|
return paymentService.getWalletWithBalance(requiredAmountSats)
|
||||||
if (!wallets.length) return null
|
|
||||||
|
|
||||||
if (requiredAmount) {
|
|
||||||
return wallets.find((wallet: any) => wallet.balance_msat >= requiredAmount * 1000) // Convert sats to msat
|
|
||||||
}
|
|
||||||
|
|
||||||
return wallets.find((wallet: any) => wallet.balance_msat > 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pay Lightning invoice with user's wallet
|
// Pay Lightning invoice with user's wallet
|
||||||
async function payInvoice(paymentRequest: string, orderId?: string): Promise<boolean> {
|
async function payInvoice(paymentRequest: string, orderId?: string): Promise<boolean> {
|
||||||
if (!isAuthenticated.value || !currentUser.value) {
|
|
||||||
throw new Error('User must be authenticated to pay with wallet')
|
|
||||||
}
|
|
||||||
|
|
||||||
const wallet = getWalletWithBalance()
|
|
||||||
if (!wallet) {
|
|
||||||
throw new Error('No wallet with sufficient balance found')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isPayingWithWallet.value = true
|
// Use PaymentService with custom success message
|
||||||
paymentError.value = null
|
const paymentResult = await paymentService.payInvoiceWithUserWallet(
|
||||||
|
paymentRequest,
|
||||||
console.log('💰 Paying invoice with wallet:', wallet.id.slice(0, 8))
|
undefined, // No specific amount requirement
|
||||||
|
{
|
||||||
// Use the same API function as events
|
successMessage: orderId
|
||||||
const paymentResult = await payInvoiceWithWallet(paymentRequest, wallet.id, wallet.adminkey)
|
? `Order ${orderId.slice(-8)} has been paid`
|
||||||
|
: 'Lightning invoice paid successfully'
|
||||||
console.log('✅ Payment successful:', {
|
}
|
||||||
paymentHash: paymentResult.payment_hash,
|
)
|
||||||
feeMsat: paymentResult.fee_msat,
|
|
||||||
orderId
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update order status to paid if orderId is provided
|
// Update order status to paid if orderId is provided
|
||||||
if (orderId) {
|
if (orderId) {
|
||||||
|
|
@ -77,72 +53,56 @@ export function useLightningPayment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toast.success('Payment successful!', {
|
|
||||||
description: orderId ? `Order ${orderId.slice(-8)} has been paid` : 'Lightning invoice paid successfully'
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Payment failed'
|
throw error // PaymentService already handled error notifications
|
||||||
paymentError.value = errorMessage
|
|
||||||
console.error('💸 Payment failed:', error)
|
|
||||||
|
|
||||||
toast.error('Payment failed', {
|
|
||||||
description: errorMessage
|
|
||||||
})
|
|
||||||
|
|
||||||
throw error
|
|
||||||
} finally {
|
|
||||||
isPayingWithWallet.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open external Lightning wallet (fallback)
|
// Open external Lightning wallet (fallback) - delegate to PaymentService
|
||||||
function openExternalLightningWallet(paymentRequest: string) {
|
function openExternalLightningWallet(paymentRequest: string) {
|
||||||
if (!paymentRequest) {
|
paymentService.openExternalLightningWallet(paymentRequest)
|
||||||
toast.error('No payment request available')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try lightning: protocol first
|
|
||||||
const lightningUrl = `lightning:${paymentRequest}`
|
|
||||||
|
|
||||||
try {
|
|
||||||
window.open(lightningUrl, '_blank')
|
|
||||||
toast.info('Opening Lightning wallet...', {
|
|
||||||
description: 'If no wallet opens, copy the payment request manually'
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Failed to open lightning: URL, showing payment request for copy:', error)
|
|
||||||
// Fallback: copy to clipboard or show for manual copy
|
|
||||||
navigator.clipboard?.writeText(paymentRequest).then(() => {
|
|
||||||
toast.success('Payment request copied to clipboard')
|
|
||||||
}).catch(() => {
|
|
||||||
toast.info('Please copy the payment request manually')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Main payment handler - tries wallet first, falls back to external
|
// Main payment handler - tries wallet first, falls back to external
|
||||||
async function handlePayment(paymentRequest: string, orderId?: string): Promise<void> {
|
async function handlePayment(paymentRequest: string, orderId?: string): Promise<void> {
|
||||||
if (!paymentRequest) {
|
try {
|
||||||
toast.error('No payment request available')
|
// Use PaymentService's automatic fallback handling
|
||||||
return
|
const result = await paymentService.handlePaymentWithFallback(
|
||||||
}
|
paymentRequest,
|
||||||
|
undefined, // No specific amount requirement
|
||||||
|
{
|
||||||
|
successMessage: orderId
|
||||||
|
? `Order ${orderId.slice(-8)} has been paid`
|
||||||
|
: 'Lightning invoice paid successfully'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Try wallet payment first if user has balance
|
// If payment was successful with wallet, update order
|
||||||
if (hasWalletWithBalance.value) {
|
if (result && orderId) {
|
||||||
try {
|
const order = marketStore.orders[orderId]
|
||||||
await payInvoice(paymentRequest, orderId)
|
if (order) {
|
||||||
return // Payment successful with wallet
|
const updatedOrder = {
|
||||||
} catch (error) {
|
...order,
|
||||||
console.log('Wallet payment failed, offering external wallet option:', error)
|
paymentStatus: 'paid' as const,
|
||||||
// Don't throw here, continue to external wallet option
|
status: 'paid' as const,
|
||||||
|
paidAt: Math.floor(Date.now() / 1000),
|
||||||
|
paymentHash: result.payment_hash,
|
||||||
|
feeMsat: result.fee_msat,
|
||||||
|
items: [...order.items], // Convert readonly to mutable
|
||||||
|
shippingZone: order.shippingZone ? {
|
||||||
|
...order.shippingZone,
|
||||||
|
countries: order.shippingZone.countries ? [...order.shippingZone.countries] : undefined
|
||||||
|
} : order.shippingZone
|
||||||
|
}
|
||||||
|
marketStore.updateOrder(orderId, updatedOrder)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// If result is null, external wallet was opened (fallback case)
|
||||||
|
} catch (error) {
|
||||||
|
// PaymentService already handled error notifications
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to external wallet
|
|
||||||
openExternalLightningWallet(paymentRequest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue