feat: Add market integration roadmap to NOSTR architecture documentation

- Introduce a comprehensive roadmap for integrating nostr-market-app purchasing functionality into the web-app.
- Outline key components of the shopping cart system, checkout process, and order management.
- Detail phased implementation strategy, including enhanced user experience and advanced features.
- Include security, performance, and testing considerations to ensure robust integration.

feat: Enhance market store with new order and cart management features

- Introduce new interfaces for Order, OrderItem, ContactInfo, and ShippingZone to support enhanced order management.
- Update Stall and Product interfaces to include currency and shipping details.
- Implement a comprehensive shopping cart system with stall-specific carts, including methods for adding, removing, and updating items.
- Add payment-related interfaces and methods for managing payment requests and statuses.
- Enhance filter options to include in-stock status and payment methods, improving product filtering capabilities.
- Refactor computed properties and methods for better cart management and checkout processes.

feat: Implement shopping cart functionality with new components and routing

- Add ShoppingCart, CartItem, and CartSummary components to manage cart items and display summaries.
- Introduce Cart.vue page to serve as the main shopping cart interface, integrating cart and summary components.
- Update Navbar.vue to include a cart icon with item count, enhancing user navigation.
- Implement cart management features in the market store, including item addition, quantity updates, and removal.
- Establish routing for the cart page, ensuring seamless navigation for users.
- Enhance ProductCard.vue to support adding items to the cart directly from the product listing.

feat: Update cart and checkout functionality with improved navigation and button labels

- Change "Proceed to Checkout" button text to dynamic "Place Order" based on context in CartSummary.vue.
- Update "Continue Shopping" button to "Back to Cart" in CartSummary.vue for clearer navigation.
- Modify routing for checkout to include stall ID in ShoppingCart.vue, enhancing checkout process.
- Simplify Cart.vue by removing CartSummary component and focusing on ShoppingCart display.
- Add new route for checkout with stall ID in router configuration for better handling of checkout flows.

feat: Enhance cart and checkout components with improved shipping address handling

- Update CartSummary.vue to use readonly types for cart items and shipping zones, ensuring immutability.
- Modify Checkout.vue to conditionally display the shipping address field based on the selected shipping zone's requirements for physical shipping.
- Add a digital delivery note for products that do not require a shipping address.
- Introduce a computed property to determine if a shipping address is required, improving validation logic during checkout.
- Update market store to include a new property for shipping zones indicating if physical shipping is required.

feat: Implement order placement functionality in checkout process

- Add a "Place Order" button in Checkout.vue that triggers the order placement process.
- Introduce loading state during order placement to enhance user experience.
- Implement createAndPlaceOrder method in market store to handle order creation and status updates.
- Include error handling for order placement failures, providing user feedback on errors.
- Update checkout logic to validate shipping zone and contact information before proceeding.

feat: Add Order History page and update Navbar for order tracking

- Introduce a new OrderHistory.vue page to display users' past orders with filtering and sorting options.
- Update Navbar.vue to include an "Order History" option with a badge showing the count of orders.
- Implement computed properties for order count and enhance user navigation experience.

feat: Integrate Nostr functionality for order management and user notifications

- Add NostrExtensionGuide component to inform users about the required Nostr extension for order transmission.
- Implement useNostrOrders composable to manage Nostr connection, event creation, and order sending.
- Update Checkout.vue to display Nostr connection status and provide feedback on order transmission.
- Enhance OrderHistory.vue to show Nostr transmission status and details for each order.
- Modify market store to handle Nostr event details and errors during order placement, ensuring local fallback.
- Introduce types for Nostr events to improve type safety and integration with the existing order management system.

refactor: Update Nostr relay configuration to use environment variable

- Change DEFAULT_RELAYS to dynamically retrieve relay URLs from the VITE_MARKET_RELAYS environment variable.
- Add error handling to ensure relays are configured before establishing a connection.
- Modify createBlankEvent function to return a more precise type.
- Update event signing process to ensure the event ID is generated correctly before signing.

refactor: useAuth switch Enhance Nostr order management with authentication checks

- Integrate user authentication checks to ensure Nostr features are only accessible to authenticated users.
- Replace direct window.nostr calls with auth store methods for retrieving public and private keys.
- Implement a helper function for signing events and mock encryption for order content.
- Remove obsolete Nostr type definitions to streamline the codebase.

feat: Enhance Checkout.vue with Nostr processing feedback and cleanup

- Update the checkout button to disable based on order placement state.
- Simplify order placement feedback by removing unnecessary Nostr processing checks.
- Introduce a new visual indicator for Nostr order processing status.
- Refactor computed properties for better clarity and efficiency in shipping zone handling.

refactor: Streamline Nostr order handling and integrate buyer public key retrieval

- Remove redundant Nostr relay tag from order event creation in useNostrOrders.
- Update Checkout.vue to retrieve the buyer's public key from the auth store, enhancing order placement logic.
- Modify createAndPlaceOrder method in market store to accept an optional Nostr orders instance for improved flexibility in order processing.

refactor: Remove Nostr-related components and streamline order processing

- Delete NostrExtensionGuide.vue and associated type definitions to simplify the codebase.
- Remove unused useNostr.ts file and related logic from useNostrOrders.ts.
- Update order handling in market store to directly integrate Nostr publishing without relying on external components.
- Enhance Checkout.vue and Cart.vue to reflect changes in Nostr integration and provide clearer order status feedback.

feat: Enhance Nostr chat functionality with malformed message handling

- Introduce tracking for malformed message IDs to prevent repeated processing attempts.
- Implement functions to mark messages as malformed, clean up old entries, and retrieve statistics on malformed messages.
- Add periodic cleanup of malformed messages to manage memory usage.
- Enhance message processing logic to skip previously identified malformed messages and provide detailed error handling for decryption failures.
- Update the return object to include new functions for managing malformed messages.

ZZ feat: Implement Lightning invoice management in market store

- Add functionality to create and manage Lightning invoices for orders.
- Introduce payment monitoring and status updates for invoices.
- Implement payment confirmation messaging via Nostr upon successful payment.
- Enhance order interface to include new fields for Lightning invoice details and payment status.

ZZ feat: Enhance OrderHistory.vue with payment status indicators and invoice management

- Add visual indicators for payment status, including 'Paid' and
'Payment Pending' badges.
- Implement expandable payment display for orders with Lightning
invoices.
- Introduce functionality to toggle payment display and generate
Lightning invoices.
- Update order status messaging to reflect payment requirements and
invoice generation status.

ZZ feat: Enhance OrderHistory.vue with payment status indicators and invoice management

- Add visual indicators for payment status, including 'Paid' and
'Payment Pending' badges.
- Implement expandable payment display for orders with Lightning
invoices.
- Introduce functionality to toggle payment display and generate
Lightning invoices.
- Update order status messaging to reflect payment requirements and
invoice generation status.

feat: Implement order event handling in useOrderEvents composable

- Introduce useOrderEvents composable to manage subscription and processing of order-related events.
- Define order event types and interfaces for better type safety and clarity.
- Implement methods to handle payment requests, order status updates, and invoice generation.
- Enhance OrderHistory.vue to display order event subscription status and last update timestamp.
- Update market store to include order update functionality for better integration with order events.

FIX: Build errors

refactor: Update component styles and improve UI consistency across market pages

- Replace various color classes with updated design tokens for better consistency.
- Change background colors of components to align with the new design system.
- Update text colors to enhance readability and maintain a cohesive look.
- Refactor class names in CartItem.vue, CartSummary.vue, DashboardOverview.vue, and other components to use the new color scheme.
- Ensure all components reflect the updated design guidelines for a unified user experience.

refactor: Remove Order History references from Navbar component

- Eliminate order count computation and related UI elements from the Navbar.
- Streamline the Navbar by removing the Order History button and badge.
- Maintain existing functionality for other menu items, ensuring a cleaner user interface.

feat: Implement QR code generation and download functionality in PaymentDisplay component

- Add QR code generation for payment requests using the qrcode library.
- Enhance UI to display loading states and error messages during QR code generation.
- Introduce a download button for users to save the generated QR code.
- Implement logic to regenerate QR code when the invoice changes.

refactor: Replace useRelayHub with relayHubComposable across components

- Update imports in multiple components and composables to use the new relayHubComposable for better consistency and maintainability.
- Enhance OrderHistory.vue with debug information for development, displaying key states related to orders, authentication, and relay hub connectivity.
- Remove unnecessary reconnect button from RelayHubStatus.vue to streamline user interactions.
- Improve logging in useOrderEvents for better debugging and monitoring of order event subscriptions.

refactor: Update OrderHistory.vue styles for improved UI consistency

- Replace color classes with updated design tokens for better alignment with the new design system.
- Enhance readability by adjusting text colors and background styles for payment status indicators.
- Ensure a cohesive look across the component by standardizing class names and styles.

refactor: Update component styles for improved UI consistency across checkout pages

- Replace color classes with updated design tokens for better alignment with the new design system.
- Enhance readability by adjusting text colors and background styles in CartSummary.vue, PaymentDisplay.vue, Checkout.vue, and OrderHistory.vue.
- Standardize class names and styles to ensure a cohesive look across all components.

feat: Implement invoice generation and Nostr integration in MerchantStore component

- Add functionality to generate Lightning invoices for orders and send them to customers via Nostr.
- Introduce a new sendInvoiceToCustomer method to update order details and publish invoice information.
- Enhance order event handling in useOrderEvents to update existing orders with new invoice data.
- Improve error handling and logging for invoice generation and sending processes.

feat: Enhance MerchantStore and PaymentDisplay components for improved invoice handling

- Add wallet indicator in MerchantStore to display the selected wallet name during pending orders.
- Implement temporary fixes for missing buyer and seller public keys when generating invoices.
- Update invoice generation logic to utilize the first available wallet and improve error handling.
- Modify PaymentDisplay to use the new bolt11 field for payment requests and enhance date formatting.
- Refactor order event handling to ensure accurate updates and invoice management across components.

feat: Enhance order event processing in useOrderEvents composable

- Refactor processOrderEvent to handle incoming Nostr market order events with improved validation and logging.
- Implement logic to update existing orders or create new ones based on event data, ensuring accurate order management.
- Add detailed console logging for better debugging and tracking of order events and their statuses.
- Ensure compatibility with market order structure and invoice details for seamless integration with payment processing.

feat: Enhance order management with localStorage persistence

- Update createOrder method to optionally accept an order ID from events, improving order tracking.
- Convert items from readonly to mutable for better manipulation.
- Implement localStorage persistence for orders, ensuring data is saved and loaded across sessions.
- Add methods to save and load orders from localStorage, enhancing user experience and data reliability.

feat: Update invoice creation to support additional metadata and nostrmarket compatibility

- Modify createInvoice method to accept an optional extra parameter for additional metadata.
- Change invoice tag to 'nostrmarket' for improved compatibility with Nostr market.
- Include merchant and buyer public keys in the invoice data for better integration.
- Update invoice creation in market store to utilize new parameters for enhanced functionality.

feat: Enhance order and invoice handling for Nostr market compatibility

- Add originalOrderId to order events for tracking Nostr order IDs.
- Update invoice creation to utilize original Nostr order ID when generating invoices.
- Improve logging for invoice requests to LNBits, providing better visibility into the data being sent.
- Ensure compatibility with nostrmarket by adjusting order ID handling in the market store.

fix: Refine invoice creation logic for Nostr market compatibility

- Adjust order ID handling in invoice creation to prioritize originalOrderId for better compatibility with nostrmarket.
- Enhance logging to provide clearer insights into the order ID being used during invoice generation.

feat: Integrate nostrmarket service for order publishing and merchant catalog management

- Implement functionality to publish orders via the nostrmarket protocol, replacing the previous Nostr integration.
- Add methods to publish merchant catalogs, including stalls and products, to nostrmarket with event ID tracking.
- Enhance order interface to include nostrEventId for better integration with nostrmarket.
- Improve error handling and logging for nostrmarket publishing processes.

refactor: Simplify order creation logic in useOrderEvents and update contact structure in nostrmarketService

- Streamline order creation by using event.id and defaulting to 'unknown' for stallId.
- Update contact structure to include address and message, removing optional email and phone fields for clarity.
- Ensure compatibility with new order data structure for improved integration with nostrmarket.

feat: Add bech32 to hex conversion utility and integrate into nostrmarketService

- Implement a new utility function to convert bech32 keys to hex format, enhancing key handling.
- Update nostrmarketService to utilize the new conversion function for user public and private keys.
- Modify contact structure to include additional fields for improved order information management.

feat: Add nostrclient configuration to AppConfig for enhanced Nostr integration

- Introduce a new nostrclient property in AppConfig to manage Nostr client settings.
- Include url and enabled fields to configure the Nostr client connection dynamically.
- Ensure compatibility with environment variables for flexible deployment configurations.

feat: Introduce comprehensive order management and fulfillment documentation

- Add ORDER_MANAGEMENT_FULFILLMENT.md to detail the complete order lifecycle, including order states, data models, and merchant/customer interfaces.
- Implement test scripts for verifying order and payment request formats in test-nostrmarket-format.js.
- Create PaymentRequestDialog.vue for handling payment requests with dynamic options and QR code generation.
- Enhance useOrderEvents.ts to process nostrmarket protocol messages for order management.
- Update nostrmarketService.ts to handle payment requests and order status updates, ensuring seamless integration with the marketplace.
- Integrate payment request dialog in Market.vue and manage its state in the market store.

refactor: Remove obsolete test script for nostrmarket order format

- Delete test-nostrmarket-format.js as it is no longer needed for verifying order and payment request formats.
- Update PaymentRequestDialog.vue to enhance UI components and integrate QR code generation for payment requests.
- Refactor payment handling and notification logic to utilize toast notifications instead of Quasar's notify system.

feat: Enhance OrderHistory component with payment request handling and QR code generation

- Add UI elements to display payment request status and options in OrderHistory.vue.
- Implement functions to copy payment requests, open Lightning wallets, and download QR codes.
- Update nostrmarketService to generate QR codes for payment requests and manage order statuses effectively.
- Remove obsolete PaymentRequestDialog integration from Market.vue for a cleaner UI.

feat: Add debug information and toast notifications in OrderHistory component

- Introduce debug info display for payment requests and hashes in OrderHistory.vue.
- Implement toast notifications for actions like copying payment requests, opening wallets, and downloading QR codes.
- Enhance error handling with user feedback for various order-related actions.
- Remove obsolete payment request dialog methods from market store for cleaner code.

feat: Revamp CartItem and ShoppingCart components for improved layout and functionality

- Enhance CartItem.vue with responsive design for desktop and mobile views, including better organization of product details, price, quantity controls, and remove button.
- Update ShoppingCart.vue to separate desktop and mobile layouts, improving the user experience with clearer action buttons and cart summary display.
- Implement consistent styling and layout adjustments for better visual coherence across different screen sizes.
This commit is contained in:
padreug 2025-08-13 15:31:18 +02:00
parent 93ffb8bf32
commit ea5a2380f1
43 changed files with 8983 additions and 146 deletions

View file

@ -1,5 +1,10 @@
import { defineStore } from 'pinia'
import { ref, computed, readonly } from 'vue'
import { nostrOrders } from '@/composables/useNostrOrders'
import { invoiceService } from '@/lib/services/invoiceService'
import { paymentMonitor } from '@/lib/services/paymentMonitor'
import { nostrmarketService } from '@/lib/services/nostrmarketService'
import type { LightningInvoice } from '@/lib/services/invoiceService'
// Types
@ -25,7 +30,9 @@ export interface Stall {
description?: string
logo?: string
categories?: string[]
shipping?: Record<string, any>
shipping?: ShippingZone[]
currency: string
nostrEventId?: string // Nostr event ID for nostrmarket integration
}
export interface Product {
@ -41,23 +48,91 @@ export interface Product {
categories?: string[]
createdAt: number
updatedAt: number
nostrEventId?: string // Nostr event ID for nostrmarket integration
}
// Enhanced Order interface for the new system
export interface Order {
id: string
stall_id: string
product_id: string
buyer_pubkey: string
seller_pubkey: string
quantity: number
total_price: number
cartId: string
stallId: string
buyerPubkey: string
sellerPubkey: string
status: OrderStatus
items: OrderItem[]
contactInfo: ContactInfo
shippingZone: ShippingZone
paymentRequest?: string
paymentMethod: PaymentMethod
subtotal: number
shippingCost: number
total: number
currency: string
status: 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled'
payment_request?: string
created_at: number
updated_at: number
createdAt: number
updatedAt: number
// Nostr integration fields
nostrEventId?: string
nostrEventSig?: string
sentViaNostr?: boolean
nostrError?: string
originalOrderId?: string // Original order ID from Nostr event (for nostrmarket compatibility)
// Lightning invoice fields
lightningInvoice?: LightningInvoice
paymentHash?: string
paidAt?: number
paymentStatus?: 'pending' | 'paid' | 'expired'
// QR code fields
qrCodeDataUrl?: string
qrCodeLoading?: boolean
qrCodeError?: string | null
}
export interface OrderItem {
productId: string
productName: string
quantity: number
price: number
currency: string
}
export interface ContactInfo {
address?: string
email?: string
message?: string
npub?: string
}
export interface ShippingZone {
id: string
name: string
cost: number
currency: string
description?: string
estimatedDays?: string
requiresPhysicalShipping?: boolean
}
export type OrderStatus = 'pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled' | 'processing'
export type PaymentMethod = 'lightning' | 'btc_onchain'
// Cart management interfaces
export interface CartItem {
product: Product
quantity: number
stallId: string
}
export interface StallCart {
id: string
merchant: string
products: CartItem[]
subtotal: number
shippingZone?: ShippingZone
currency: string
}
// Enhanced FilterData with more options
export interface FilterData {
categories: string[]
merchants: string[]
@ -65,6 +140,8 @@ export interface FilterData {
currency: string | null
priceFrom: number | null
priceTo: number | null
inStock: boolean | null
paymentMethods: PaymentMethod[]
}
export interface SortOptions {
@ -72,6 +149,22 @@ export interface SortOptions {
order: 'asc' | 'desc'
}
// Payment-related interfaces
export interface PaymentRequest {
paymentRequest: string
amount: number
currency: string
expiresAt: number
description: string
}
export interface PaymentStatus {
paid: boolean
amountPaid: number
paidAt?: number
transactionId?: string
}
export const useMarketStore = defineStore('market', () => {
// Core market state
const markets = ref<Market[]>([])
@ -98,7 +191,9 @@ export const useMarketStore = defineStore('market', () => {
stalls: [],
currency: null,
priceFrom: null,
priceTo: null
priceTo: null,
inStock: null,
paymentMethods: []
})
const sortOptions = ref<SortOptions>({
@ -106,9 +201,21 @@ export const useMarketStore = defineStore('market', () => {
order: 'asc'
})
// Shopping cart
// Enhanced shopping cart with stall-specific carts
const stallCarts = ref<Record<string, StallCart>>({})
// Legacy shopping cart (to be deprecated)
const shoppingCart = ref<Record<string, { product: Product; quantity: number }>>({})
// Checkout state
const checkoutCart = ref<StallCart | null>(null)
const checkoutStall = ref<Stall | null>(null)
const activeOrder = ref<Order | null>(null)
// Payment state
const paymentRequest = ref<PaymentRequest | null>(null)
const paymentStatus = ref<PaymentStatus | null>(null)
// Computed properties
const filteredProducts = computed(() => {
let filtered = products.value
@ -164,6 +271,20 @@ export const useMarketStore = defineStore('market', () => {
)
}
// In stock filter
if (filterData.value.inStock !== null) {
filtered = filtered.filter(product =>
filterData.value.inStock ? product.quantity > 0 : product.quantity === 0
)
}
// Payment methods filter
if (filterData.value.paymentMethods.length > 0) {
// For now, assume all products support Lightning payments
// This can be enhanced later with product-specific payment method support
filtered = filtered.filter(_product => true)
}
// Sort
filtered.sort((a, b) => {
const aVal = a[sortOptions.value.field as keyof Product]
@ -199,6 +320,27 @@ export const useMarketStore = defineStore('market', () => {
}))
})
// Enhanced cart computed properties
const allStallCarts = computed(() => Object.values(stallCarts.value))
const totalCartItems = computed(() => {
return allStallCarts.value.reduce((total, cart) => {
return total + cart.products.reduce((cartTotal, item) => cartTotal + item.quantity, 0)
}, 0)
})
const totalCartValue = computed(() => {
return allStallCarts.value.reduce((total, cart) => {
return total + cart.subtotal
}, 0)
})
const activeStallCart = computed(() => {
if (!checkoutStall.value) return null
return stallCarts.value[checkoutStall.value.id] || null
})
// Legacy cart computed properties (to be deprecated)
const cartTotal = computed(() => {
return Object.values(shoppingCart.value).reduce((total, item) => {
return total + (item.product.price * item.quantity)
@ -291,6 +433,448 @@ export const useMarketStore = defineStore('market', () => {
shoppingCart.value = {}
}
// Enhanced cart management methods
const addToStallCart = (product: Product, quantity: number = 1) => {
const stallId = product.stall_id
const stall = stalls.value.find(s => s.id === stallId)
if (!stall) {
console.error('Stall not found for product:', product.id)
return
}
// Initialize stall cart if it doesn't exist
if (!stallCarts.value[stallId]) {
stallCarts.value[stallId] = {
id: stallId,
merchant: stall.pubkey,
products: [],
subtotal: 0,
currency: stall.currency || 'sats'
}
}
const cart = stallCarts.value[stallId]
const existingItem = cart.products.find(item => item.product.id === product.id)
if (existingItem) {
existingItem.quantity = Math.min(existingItem.quantity + quantity, product.quantity)
} else {
cart.products.push({
product,
quantity: Math.min(quantity, product.quantity),
stallId
})
}
// Update cart subtotal
updateStallCartSubtotal(stallId)
}
const removeFromStallCart = (stallId: string, productId: string) => {
const cart = stallCarts.value[stallId]
if (cart) {
cart.products = cart.products.filter(item => item.product.id !== productId)
updateStallCartSubtotal(stallId)
// Remove empty carts
if (cart.products.length === 0) {
delete stallCarts.value[stallId]
}
}
}
const updateStallCartQuantity = (stallId: string, productId: string, quantity: number) => {
const cart = stallCarts.value[stallId]
if (cart) {
if (quantity <= 0) {
removeFromStallCart(stallId, productId)
} else {
const item = cart.products.find(item => item.product.id === productId)
if (item) {
item.quantity = Math.min(quantity, item.product.quantity)
updateStallCartSubtotal(stallId)
}
}
}
}
const updateStallCartSubtotal = (stallId: string) => {
const cart = stallCarts.value[stallId]
if (cart) {
cart.subtotal = cart.products.reduce((total, item) => {
return total + (item.product.price * item.quantity)
}, 0)
}
}
const clearStallCart = (stallId: string) => {
delete stallCarts.value[stallId]
}
const clearAllStallCarts = () => {
stallCarts.value = {}
}
const setCheckoutCart = (stallId: string) => {
const cart = stallCarts.value[stallId]
const stall = stalls.value.find(s => s.id === stallId)
if (cart && stall) {
checkoutCart.value = cart
checkoutStall.value = stall
}
}
const clearCheckout = () => {
checkoutCart.value = null
checkoutStall.value = null
activeOrder.value = null
paymentRequest.value = null
paymentStatus.value = null
}
const setShippingZone = (stallId: string, shippingZone: ShippingZone) => {
const cart = stallCarts.value[stallId]
if (cart) {
cart.shippingZone = shippingZone
}
}
// Order management methods
const createOrder = (orderData: Omit<Order, 'id' | 'createdAt' | 'updatedAt'> & { id?: string }) => {
const order: Order = {
...orderData,
id: orderData.id || generateOrderId(),
createdAt: Math.floor(Date.now() / 1000),
updatedAt: Math.floor(Date.now() / 1000)
}
orders.value[order.id] = order
activeOrder.value = order
// Save to localStorage for persistence
saveOrdersToStorage()
return order
}
const createAndPlaceOrder = async (orderData: Omit<Order, 'id' | 'createdAt' | 'updatedAt'>) => {
try {
// Create the order
const order = createOrder(orderData)
// Attempt to publish order via nostrmarket protocol
let nostrmarketSuccess = false
let nostrmarketError: string | undefined
try {
// Publish the order event to nostrmarket using proper protocol
const eventId = await nostrmarketService.publishOrder(order, order.sellerPubkey)
nostrmarketSuccess = true
order.sentViaNostr = true
order.nostrEventId = eventId
console.log('Order published via nostrmarket successfully:', eventId)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown nostrmarket error'
order.nostrError = errorMessage
order.sentViaNostr = false
console.error('Failed to publish order via nostrmarket:', errorMessage)
}
// Update order status to 'pending'
updateOrderStatus(order.id, 'pending')
// Clear the checkout cart
if (checkoutCart.value) {
clearStallCart(checkoutCart.value.id)
}
// Clear checkout state
clearCheckout()
// Show appropriate success/error message
if (nostrmarketSuccess) {
console.log('Order created and published via nostrmarket successfully')
} else {
console.warn('Order created but nostrmarket publishing failed:', nostrmarketError)
}
return order
} catch (error) {
console.error('Failed to create and place order:', error)
throw new Error('Failed to place order. Please try again.')
}
}
// nostrmarket integration methods
const publishToNostrmarket = async () => {
try {
console.log('Publishing merchant catalog to nostrmarket...')
// Get all stalls and products
const allStalls = Object.values(stalls.value)
const allProducts = Object.values(products.value)
if (allStalls.length === 0) {
console.warn('No stalls to publish to nostrmarket')
return null
}
if (allProducts.length === 0) {
console.warn('No products to publish to nostrmarket')
return null
}
// Publish to nostrmarket
const result = await nostrmarketService.publishMerchantCatalog(allStalls, allProducts)
console.log('Successfully published to nostrmarket:', result)
// Update stalls and products with event IDs
for (const [stallId, eventId] of Object.entries(result.stalls)) {
const stall = stalls.value.find(s => s.id === stallId)
if (stall) {
stall.nostrEventId = eventId
}
}
for (const [productId, eventId] of Object.entries(result.products)) {
const product = products.value.find(p => p.id === productId)
if (product) {
product.nostrEventId = eventId
}
}
return result
} catch (error) {
console.error('Failed to publish to nostrmarket:', error)
throw error
}
}
// Invoice management methods
const createLightningInvoice = async (orderId: string, adminKey: string): Promise<LightningInvoice | null> => {
try {
const order = orders.value[orderId]
if (!order) {
throw new Error('Order not found')
}
// Create Lightning invoice with admin key and nostrmarket tag
// For nostrmarket compatibility, we need to use the original order ID if it exists
// If no originalOrderId exists, this order was created in the web-app, so use the current orderId
const orderIdForInvoice = order.originalOrderId || orderId
console.log('Creating invoice with order ID:', {
webAppOrderId: orderId,
originalOrderId: order.originalOrderId,
orderIdForInvoice: orderIdForInvoice,
hasOriginalOrderId: !!order.originalOrderId
})
const invoice = await invoiceService.createInvoice(order, adminKey, {
tag: "nostrmarket",
order_id: orderIdForInvoice, // Use original Nostr order ID for nostrmarket compatibility
merchant_pubkey: order.sellerPubkey,
buyer_pubkey: order.buyerPubkey
})
// Update order with invoice details
order.lightningInvoice = invoice
order.paymentHash = invoice.payment_hash
order.paymentStatus = 'pending'
order.paymentRequest = invoice.bolt11 // Use bolt11 field from LNBits response
// Save to localStorage after invoice creation
saveOrdersToStorage()
// Start monitoring payment
await paymentMonitor.startMonitoring(order, invoice)
// Set up payment update callback
paymentMonitor.onPaymentUpdate(orderId, (update) => {
handlePaymentUpdate(orderId, update)
})
console.log('Lightning invoice created for order:', {
orderId,
originalOrderId: order.originalOrderId,
nostrmarketOrderId: order.originalOrderId || orderId,
paymentHash: invoice.payment_hash,
amount: invoice.amount
})
return invoice
} catch (error) {
console.error('Failed to create Lightning invoice:', error)
throw new Error('Failed to create payment invoice')
}
}
const handlePaymentUpdate = (orderId: string, update: any) => {
const order = orders.value[orderId]
if (!order) return
// Update order payment status
order.paymentStatus = update.status
if (update.status === 'paid') {
order.paidAt = update.paidAt
updateOrderStatus(orderId, 'paid')
// Send payment confirmation via Nostr
sendPaymentConfirmation(order)
}
// Save to localStorage after payment update
saveOrdersToStorage()
console.log('Payment status updated for order:', {
orderId,
status: update.status,
amount: update.amount
})
}
const sendPaymentConfirmation = async (order: Order) => {
try {
if (!nostrOrders.isReady.value) {
console.warn('Nostr not ready for payment confirmation')
return
}
// Create payment confirmation message
// const confirmation = {
// type: 'payment_confirmation',
// orderId: order.id,
// paymentHash: order.paymentHash,
// amount: order.total,
// currency: order.currency,
// paidAt: order.paidAt,
// message: 'Payment received! Your order is being processed.'
// }
// Send confirmation to customer
await nostrOrders.publishOrderEvent(order, order.buyerPubkey)
console.log('Payment confirmation sent via Nostr')
} catch (error) {
console.error('Failed to send payment confirmation:', error)
}
}
const getOrderInvoice = (orderId: string): LightningInvoice | null => {
const order = orders.value[orderId]
return order?.lightningInvoice || null
}
const getOrderPaymentStatus = (orderId: string): 'pending' | 'paid' | 'expired' | null => {
const order = orders.value[orderId]
return order?.paymentStatus || null
}
const updateOrderStatus = (orderId: string, status: OrderStatus) => {
const order = orders.value[orderId]
if (order) {
order.status = status
order.updatedAt = Date.now() / 1000
saveOrdersToStorage()
}
}
const updateOrder = (orderId: string, updatedOrder: Partial<Order>) => {
const order = orders.value[orderId]
if (order) {
Object.assign(order, updatedOrder)
order.updatedAt = Date.now() / 1000
saveOrdersToStorage()
}
}
const setPaymentRequest = (request: PaymentRequest) => {
paymentRequest.value = request
}
const setPaymentStatus = (status: PaymentStatus) => {
paymentStatus.value = status
}
// Utility methods
const generateOrderId = () => {
return `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
// Persistence methods
const saveOrdersToStorage = () => {
try {
localStorage.setItem('market_orders', JSON.stringify(orders.value))
} catch (error) {
console.warn('Failed to save orders to localStorage:', error)
}
}
const loadOrdersFromStorage = () => {
try {
const stored = localStorage.getItem('market_orders')
if (stored) {
const parsedOrders = JSON.parse(stored)
orders.value = parsedOrders
console.log('Loaded orders from localStorage:', Object.keys(parsedOrders).length)
}
} catch (error) {
console.warn('Failed to load orders from localStorage:', error)
}
}
// Payment utility methods
const calculateOrderTotal = (cart: StallCart, shippingZone?: ShippingZone) => {
const subtotal = cart.subtotal
const shippingCost = shippingZone?.cost || 0
return subtotal + shippingCost
}
const validateCartForCheckout = (stallId: string): { valid: boolean; errors: string[] } => {
const cart = stallCarts.value[stallId]
const errors: string[] = []
if (!cart || cart.products.length === 0) {
errors.push('Cart is empty')
return { valid: false, errors }
}
// Check if all products are still in stock
for (const item of cart.products) {
if (item.quantity > item.product.quantity) {
errors.push(`${item.product.name} is out of stock`)
}
}
// Check if cart has shipping zone selected
if (!cart.shippingZone) {
errors.push('Please select a shipping zone')
}
return { valid: errors.length === 0, errors }
}
const getCartSummary = (stallId: string) => {
const cart = stallCarts.value[stallId]
if (!cart) return null
const itemCount = cart.products.reduce((total, item) => total + item.quantity, 0)
const subtotal = cart.subtotal
const shippingCost = cart.shippingZone?.cost || 0
const total = subtotal + shippingCost
return {
itemCount,
subtotal,
shippingCost,
total,
currency: cart.currency
}
}
const updateFilterData = (newFilterData: Partial<FilterData>) => {
filterData.value = { ...filterData.value, ...newFilterData }
}
@ -302,7 +886,9 @@ export const useMarketStore = defineStore('market', () => {
stalls: [],
currency: null,
priceFrom: null,
priceTo: null
priceTo: null,
inStock: null,
paymentMethods: []
}
}
@ -329,6 +915,9 @@ export const useMarketStore = defineStore('market', () => {
}).format(price)
}
// Initialize orders from localStorage
loadOrdersFromStorage()
return {
// State
markets: readonly(markets),
@ -346,10 +935,20 @@ export const useMarketStore = defineStore('market', () => {
filterData: readonly(filterData),
sortOptions: readonly(sortOptions),
shoppingCart: readonly(shoppingCart),
stallCarts: readonly(stallCarts),
checkoutCart: readonly(checkoutCart),
checkoutStall: readonly(checkoutStall),
activeOrder: readonly(activeOrder),
paymentRequest: readonly(paymentRequest),
paymentStatus: readonly(paymentStatus),
// Computed
filteredProducts,
allCategories,
allStallCarts,
totalCartItems,
totalCartValue,
activeStallCart,
cartTotal,
cartItemCount,
@ -371,6 +970,33 @@ export const useMarketStore = defineStore('market', () => {
clearFilters,
toggleCategoryFilter,
updateSortOptions,
formatPrice
formatPrice,
addToStallCart,
removeFromStallCart,
updateStallCartQuantity,
updateStallCartSubtotal,
clearStallCart,
clearAllStallCarts,
setCheckoutCart,
clearCheckout,
setShippingZone,
createOrder,
updateOrderStatus,
setPaymentRequest,
setPaymentStatus,
generateOrderId,
calculateOrderTotal,
validateCartForCheckout,
getCartSummary,
createAndPlaceOrder,
createLightningInvoice,
handlePaymentUpdate,
sendPaymentConfirmation,
getOrderInvoice,
getOrderPaymentStatus,
updateOrder,
saveOrdersToStorage,
loadOrdersFromStorage,
publishToNostrmarket
}
})