314 lines
9.3 KiB
TypeScript
314 lines
9.3 KiB
TypeScript
import { ref, computed } from 'vue'
|
|
import { nip04 } from 'nostr-tools'
|
|
import { relayHubComposable } from './useRelayHub'
|
|
import { useAuth } from './useAuth'
|
|
import { useMarketStore } from '@/stores/market'
|
|
import { decode } from 'light-bolt11-decoder'
|
|
|
|
// Nostrmarket Order interfaces based on the actual implementation
|
|
|
|
// Nostrmarket Order interfaces based on the actual implementation
|
|
interface OrderItem {
|
|
product_id: string
|
|
quantity: number
|
|
}
|
|
|
|
interface OrderContact {
|
|
nostr?: string
|
|
phone?: string
|
|
email?: string
|
|
}
|
|
|
|
// Direct message types from nostrmarket
|
|
enum DirectMessageType {
|
|
PLAIN_TEXT = -1,
|
|
CUSTOMER_ORDER = 0,
|
|
PAYMENT_REQUEST = 1,
|
|
ORDER_PAID_OR_SHIPPED = 2
|
|
}
|
|
|
|
// Event types for nostrmarket protocol
|
|
interface CustomerOrderEvent {
|
|
type: DirectMessageType.CUSTOMER_ORDER
|
|
id: string
|
|
items: OrderItem[]
|
|
contact?: OrderContact
|
|
shipping_id: string
|
|
message?: string
|
|
}
|
|
|
|
interface PaymentRequestEvent {
|
|
type: DirectMessageType.PAYMENT_REQUEST
|
|
id: string
|
|
message?: string
|
|
payment_options: Array<{
|
|
type: string
|
|
link: string
|
|
}>
|
|
}
|
|
|
|
interface OrderStatusEvent {
|
|
type: DirectMessageType.ORDER_PAID_OR_SHIPPED
|
|
id: string
|
|
message?: string
|
|
paid?: boolean
|
|
shipped?: boolean
|
|
}
|
|
|
|
// Helper function to extract expiry from bolt11 invoice
|
|
function extractExpiryFromBolt11(bolt11String: string): string | undefined {
|
|
try {
|
|
const decoded = decode(bolt11String)
|
|
console.log('Decoded bolt11 invoice:', {
|
|
amount: decoded.sections.find(section => section.name === 'amount')?.value,
|
|
expiry: decoded.expiry,
|
|
timestamp: decoded.sections.find(section => section.name === 'timestamp')?.value
|
|
})
|
|
|
|
// Calculate expiry date from timestamp + expiry seconds
|
|
const timestamp = decoded.sections.find(section => section.name === 'timestamp')?.value as number
|
|
const expirySeconds = decoded.expiry as number
|
|
|
|
if (timestamp && expirySeconds) {
|
|
const expiryDate = new Date((timestamp + expirySeconds) * 1000)
|
|
return expiryDate.toISOString()
|
|
}
|
|
|
|
return undefined
|
|
} catch (error) {
|
|
console.warn('Failed to extract expiry from bolt11:', error)
|
|
return undefined
|
|
}
|
|
}
|
|
|
|
export function useOrderEvents() {
|
|
const relayHub = relayHubComposable
|
|
const auth = useAuth()
|
|
const marketStore = useMarketStore()
|
|
|
|
// State
|
|
const isSubscribed = ref(false)
|
|
const lastEventTimestamp = ref(0)
|
|
const processedEventIds = ref(new Set<string>())
|
|
const subscriptionId = ref<string | null>(null)
|
|
|
|
// Computed
|
|
const currentUserPubkey = computed(() => auth.currentUser?.value?.pubkey)
|
|
const isReady = computed(() => {
|
|
const isAuth = auth.isAuthenticated
|
|
const isConnected = relayHub.isConnected.value
|
|
const hasPubkey = !!currentUserPubkey.value
|
|
|
|
return isAuth && isConnected && hasPubkey
|
|
})
|
|
|
|
// Subscribe to order events
|
|
const subscribeToOrderEvents = async () => {
|
|
if (!isReady.value || isSubscribed.value) {
|
|
return
|
|
}
|
|
|
|
try {
|
|
// Subscribe to direct messages (kind 4) that contain order information
|
|
const filters = [
|
|
{
|
|
kinds: [4], // NIP-04 encrypted direct messages
|
|
'#p': [currentUserPubkey.value].filter(Boolean) as string[],
|
|
since: lastEventTimestamp.value
|
|
}
|
|
]
|
|
|
|
relayHub.subscribe({
|
|
id: 'order-events',
|
|
filters,
|
|
onEvent: handleOrderEvent,
|
|
onEose: () => {
|
|
console.log('Order events subscription ended')
|
|
}
|
|
})
|
|
|
|
subscriptionId.value = 'order-events'
|
|
isSubscribed.value = true
|
|
console.log('Successfully subscribed to order events')
|
|
|
|
} catch (error) {
|
|
console.error('Failed to subscribe to order events:', error)
|
|
}
|
|
}
|
|
|
|
// Handle incoming order events
|
|
const handleOrderEvent = async (event: any) => {
|
|
if (processedEventIds.value.has(event.id)) {
|
|
return
|
|
}
|
|
|
|
processedEventIds.value.add(event.id)
|
|
lastEventTimestamp.value = Math.max(lastEventTimestamp.value, event.created_at)
|
|
|
|
try {
|
|
// Decrypt the message content
|
|
const decryptedContent = await nip04.decrypt(
|
|
auth.currentUser.value?.prvkey || '',
|
|
event.pubkey,
|
|
event.content
|
|
)
|
|
|
|
// Parse the JSON content
|
|
const jsonData = JSON.parse(decryptedContent)
|
|
|
|
// Handle different message types
|
|
switch (jsonData.type) {
|
|
case DirectMessageType.CUSTOMER_ORDER:
|
|
await handleCustomerOrder(jsonData as CustomerOrderEvent, event.pubkey)
|
|
break
|
|
case DirectMessageType.PAYMENT_REQUEST:
|
|
await handlePaymentRequest(jsonData as PaymentRequestEvent, event.pubkey)
|
|
break
|
|
case DirectMessageType.ORDER_PAID_OR_SHIPPED:
|
|
await handleOrderStatusUpdate(jsonData as OrderStatusEvent, event.pubkey)
|
|
break
|
|
default:
|
|
console.log('Unknown message type:', jsonData.type)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error processing order event:', error)
|
|
}
|
|
}
|
|
|
|
// Handle customer order (type 0)
|
|
const handleCustomerOrder = async (orderData: CustomerOrderEvent, _senderPubkey: string) => {
|
|
console.log('Received customer order:', orderData)
|
|
|
|
// Create a basic order object from the event data
|
|
const order = {
|
|
id: orderData.id,
|
|
type: DirectMessageType.CUSTOMER_ORDER,
|
|
items: orderData.items,
|
|
contact: orderData.contact,
|
|
shipping_id: orderData.shipping_id,
|
|
message: orderData.message,
|
|
createdAt: Date.now(),
|
|
updatedAt: Date.now()
|
|
}
|
|
|
|
// Store the order in our local state
|
|
// Note: We're not using the complex Order interface from market store
|
|
// Instead, we're using the simple nostrmarket format
|
|
console.log('Processed customer order:', order)
|
|
}
|
|
|
|
// Handle payment request (type 1)
|
|
const handlePaymentRequest = async (paymentData: PaymentRequestEvent, _senderPubkey: string) => {
|
|
console.log('Received payment request:', paymentData)
|
|
|
|
// Find the lightning payment option
|
|
const lightningOption = paymentData.payment_options?.find(opt => opt.type === 'ln')
|
|
if (lightningOption) {
|
|
console.log('Lightning payment request:', lightningOption.link)
|
|
|
|
// Find the existing order by ID
|
|
const existingOrder = marketStore.orders[paymentData.id]
|
|
if (existingOrder) {
|
|
console.log('Found existing order, updating with payment request:', existingOrder.id)
|
|
|
|
// Try to extract actual expiry from bolt11
|
|
const actualExpiry = extractExpiryFromBolt11(lightningOption.link)
|
|
|
|
// Create lightning invoice object
|
|
const lightningInvoice = {
|
|
checking_id: '', // Will be extracted from bolt11 if needed
|
|
payment_hash: '', // Will be extracted from bolt11 if needed
|
|
wallet_id: '', // Not available from payment request
|
|
amount: existingOrder.total,
|
|
fee: 0, // Not available from payment request
|
|
bolt11: lightningOption.link,
|
|
status: 'pending',
|
|
memo: paymentData.message || 'Payment for order',
|
|
created_at: new Date().toISOString(),
|
|
expiry: actualExpiry // Use actual expiry from bolt11 decoding
|
|
}
|
|
|
|
// Update the order with the lightning invoice
|
|
marketStore.updateOrder(existingOrder.id, {
|
|
lightningInvoice,
|
|
status: 'pending',
|
|
paymentRequest: lightningOption.link,
|
|
updatedAt: Date.now()
|
|
})
|
|
|
|
console.log('Order updated with payment request:', existingOrder.id)
|
|
} else {
|
|
console.warn('Order not found for payment request:', paymentData.id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle order status update (type 2)
|
|
const handleOrderStatusUpdate = async (statusData: OrderStatusEvent, _senderPubkey: string) => {
|
|
console.log('Received order status update:', statusData)
|
|
|
|
// Update order status in local state
|
|
if (statusData.paid !== undefined) {
|
|
console.log(`Order ${statusData.id} payment status: ${statusData.paid}`)
|
|
}
|
|
if (statusData.shipped !== undefined) {
|
|
console.log(`Order ${statusData.id} shipping status: ${statusData.shipped}`)
|
|
}
|
|
}
|
|
|
|
// Unsubscribe from order events
|
|
const unsubscribeFromOrderEvents = () => {
|
|
if (subscriptionId.value) {
|
|
relayHub.cleanup()
|
|
subscriptionId.value = null
|
|
}
|
|
isSubscribed.value = false
|
|
console.log('Unsubscribed from order events')
|
|
}
|
|
|
|
// Watch for ready state changes
|
|
const watchReadyState = () => {
|
|
if (isReady.value && !isSubscribed.value) {
|
|
subscribeToOrderEvents()
|
|
} else if (!isReady.value && isSubscribed.value) {
|
|
unsubscribeFromOrderEvents()
|
|
}
|
|
}
|
|
|
|
// Watch for authentication changes
|
|
const watchAuthChanges = () => {
|
|
if (auth.isAuthenticated && relayHub.isConnected.value) {
|
|
subscribeToOrderEvents()
|
|
} else {
|
|
unsubscribeFromOrderEvents()
|
|
}
|
|
}
|
|
|
|
// Initialize subscription when ready
|
|
const initialize = () => {
|
|
if (isReady.value) {
|
|
subscribeToOrderEvents()
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
const cleanup = () => {
|
|
unsubscribeFromOrderEvents()
|
|
processedEventIds.value.clear()
|
|
}
|
|
|
|
return {
|
|
// State
|
|
isSubscribed: computed(() => isSubscribed.value),
|
|
isReady: computed(() => isReady.value),
|
|
lastEventTimestamp: computed(() => lastEventTimestamp.value),
|
|
|
|
// Methods
|
|
subscribeToOrderEvents,
|
|
unsubscribeFromOrderEvents,
|
|
initialize,
|
|
cleanup,
|
|
watchReadyState,
|
|
watchAuthChanges
|
|
}
|
|
}
|