Implement Lightning payment handling in OrderHistory component
- Replace the existing openLightningWallet function with payWithLightning for improved payment processing. - Introduce useLightningPayment composable to manage wallet payment logic and state. - Update button text dynamically based on payment status and wallet availability. - Enhance error handling and user feedback during payment attempts, ensuring a smoother user experience.
This commit is contained in:
parent
f5ea2a8d5e
commit
db9b50240d
2 changed files with 171 additions and 26 deletions
|
|
@ -137,10 +137,10 @@
|
||||||
|
|
||||||
<!-- Payment Actions -->
|
<!-- Payment Actions -->
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<Button @click="openLightningWallet(order.paymentRequest || '')" variant="default" size="sm"
|
<Button @click="payWithLightning(order)" variant="default" size="sm"
|
||||||
class="flex-1" :disabled="!order.paymentRequest">
|
class="flex-1" :disabled="!order.paymentRequest || isPayingWithWallet">
|
||||||
<Zap class="w-3 h-3 mr-1" />
|
<Zap class="w-3 h-3 mr-1" />
|
||||||
Pay with Lightning
|
{{ isPayingWithWallet ? 'Paying...' : hasWalletWithBalance ? 'Pay with Wallet' : 'Pay with Lightning' }}
|
||||||
</Button>
|
</Button>
|
||||||
<Button @click="toggleQRCode(order.id)" variant="outline" size="sm">
|
<Button @click="toggleQRCode(order.id)" variant="outline" size="sm">
|
||||||
<QrCode class="w-3 h-3" />
|
<QrCode class="w-3 h-3" />
|
||||||
|
|
@ -230,10 +230,11 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useMarketStore } from '@/stores/market'
|
import { useMarketStore } from '../stores/market'
|
||||||
// import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module
|
// import { useOrderEvents } from '@/composables/useOrderEvents' // TODO: Move to market module
|
||||||
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
|
||||||
import { auth } from '@/composables/useAuth'
|
import { auth } from '@/composables/useAuth'
|
||||||
|
import { useLightningPayment } from '../composables/useLightningPayment'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Badge } from '@/components/ui/badge'
|
import { Badge } from '@/components/ui/badge'
|
||||||
import { Package, Store, Zap, Copy, QrCode } from 'lucide-vue-next'
|
import { Package, Store, Zap, Copy, QrCode } from 'lucide-vue-next'
|
||||||
|
|
@ -243,6 +244,8 @@ import type { OrderStatus } from '@/stores/market'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const marketStore = useMarketStore()
|
const marketStore = useMarketStore()
|
||||||
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
const relayHub = injectService(SERVICE_TOKENS.RELAY_HUB)
|
||||||
|
const { handlePayment, isPayingWithWallet, hasWalletWithBalance } = useLightningPayment()
|
||||||
|
|
||||||
// const orderEvents = useOrderEvents() // TODO: Move to market module
|
// const orderEvents = useOrderEvents() // TODO: Move to market module
|
||||||
const orderEvents = {
|
const orderEvents = {
|
||||||
isSubscribed: ref(false),
|
isSubscribed: ref(false),
|
||||||
|
|
@ -382,31 +385,14 @@ const copyPaymentRequest = async (paymentRequest: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openLightningWallet = (paymentRequest: string) => {
|
const payWithLightning = async (order: any) => {
|
||||||
// Try to open with common Lightning wallet protocols
|
|
||||||
const protocols = [
|
|
||||||
`lightning:${paymentRequest}`,
|
|
||||||
`bitcoin:${paymentRequest}`,
|
|
||||||
paymentRequest
|
|
||||||
]
|
|
||||||
|
|
||||||
// Try each protocol
|
|
||||||
for (const protocol of protocols) {
|
|
||||||
try {
|
try {
|
||||||
window.open(protocol, '_blank')
|
await handlePayment(order.paymentRequest, order.id)
|
||||||
toast.success('Opening Lightning wallet', {
|
} catch (error) {
|
||||||
description: 'If your wallet doesn\'t open, copy the payment request manually'
|
console.error('Payment failed:', error)
|
||||||
})
|
|
||||||
return
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('Failed to open protocol:', protocol, err)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback: copy to clipboard
|
|
||||||
copyPaymentRequest(paymentRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleQRCode = async (orderId: string) => {
|
const toggleQRCode = async (orderId: string) => {
|
||||||
// Toggle QR code visibility for the order
|
// Toggle QR code visibility for the order
|
||||||
const order = marketStore.orders[orderId]
|
const order = marketStore.orders[orderId]
|
||||||
|
|
|
||||||
159
src/modules/market/composables/useLightningPayment.ts
Normal file
159
src/modules/market/composables/useLightningPayment.ts
Normal file
|
|
@ -0,0 +1,159 @@
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
import { useAuth } from '@/composables/useAuth'
|
||||||
|
import { useMarketStore } from '../stores/market'
|
||||||
|
import { payInvoiceWithWallet } from '@/lib/api/events'
|
||||||
|
import { toast } from 'vue-sonner'
|
||||||
|
|
||||||
|
export function useLightningPayment() {
|
||||||
|
const { isAuthenticated, currentUser } = useAuth()
|
||||||
|
const marketStore = useMarketStore()
|
||||||
|
|
||||||
|
// State
|
||||||
|
const isPayingWithWallet = ref(false)
|
||||||
|
const paymentError = ref<string | null>(null)
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
const userWallets = computed(() => currentUser.value?.wallets || [])
|
||||||
|
const hasWalletWithBalance = computed(() =>
|
||||||
|
userWallets.value.some((wallet: any) => wallet.balance_msat > 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get wallet with sufficient balance
|
||||||
|
const getWalletWithBalance = (requiredAmount?: number) => {
|
||||||
|
const wallets = userWallets.value
|
||||||
|
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
|
||||||
|
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 {
|
||||||
|
isPayingWithWallet.value = true
|
||||||
|
paymentError.value = null
|
||||||
|
|
||||||
|
console.log('💰 Paying invoice with wallet:', wallet.id.slice(0, 8))
|
||||||
|
|
||||||
|
// Use the same API function as events
|
||||||
|
const paymentResult = await payInvoiceWithWallet(paymentRequest, wallet.id, wallet.adminkey)
|
||||||
|
|
||||||
|
console.log('✅ Payment successful:', {
|
||||||
|
paymentHash: paymentResult.payment_hash,
|
||||||
|
feeMsat: paymentResult.fee_msat,
|
||||||
|
orderId
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update order status to paid if orderId is provided
|
||||||
|
if (orderId) {
|
||||||
|
const order = marketStore.orders[orderId]
|
||||||
|
if (order) {
|
||||||
|
const updatedOrder = {
|
||||||
|
...order,
|
||||||
|
paymentStatus: 'paid' as const,
|
||||||
|
status: 'paid' as const,
|
||||||
|
paidAt: Math.floor(Date.now() / 1000),
|
||||||
|
paymentHash: paymentResult.payment_hash,
|
||||||
|
feeMsat: paymentResult.fee_msat,
|
||||||
|
items: [...order.items] // Convert readonly to mutable
|
||||||
|
}
|
||||||
|
marketStore.updateOrder(orderId, updatedOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success('Payment successful!', {
|
||||||
|
description: orderId ? `Order ${orderId.slice(-8)} has been paid` : 'Lightning invoice paid successfully'
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : 'Payment failed'
|
||||||
|
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)
|
||||||
|
function openExternalLightningWallet(paymentRequest: string) {
|
||||||
|
if (!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
|
||||||
|
async function handlePayment(paymentRequest: string, orderId?: string): Promise<void> {
|
||||||
|
if (!paymentRequest) {
|
||||||
|
toast.error('No payment request available')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try wallet payment first if user has balance
|
||||||
|
if (hasWalletWithBalance.value) {
|
||||||
|
try {
|
||||||
|
await payInvoice(paymentRequest, orderId)
|
||||||
|
return // Payment successful with wallet
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Wallet payment failed, offering external wallet option:', error)
|
||||||
|
// Don't throw here, continue to external wallet option
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to external wallet
|
||||||
|
openExternalLightningWallet(paymentRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// State
|
||||||
|
isPayingWithWallet,
|
||||||
|
paymentError,
|
||||||
|
|
||||||
|
// Computed
|
||||||
|
hasWalletWithBalance,
|
||||||
|
userWallets,
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
payInvoice,
|
||||||
|
openExternalLightningWallet,
|
||||||
|
handlePayment,
|
||||||
|
getWalletWithBalance
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue