- Reorganize all markdown documentation into structured docs/ folder - Create 7 main documentation categories (00-overview through 06-deployment) - Add comprehensive index files for each category with cross-linking - Implement Obsidian-compatible [[link]] syntax throughout - Move legacy/deprecated documentation to archive folder - Establish documentation standards and maintenance guidelines - Provide complete coverage of modular architecture, services, and deployment - Enable better navigation and discoverability for developers and contributors 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
30 KiB
Order Management & Fulfillment Workflows
This document provides comprehensive coverage of the complete order lifecycle, from initial placement through payment processing to final fulfillment and shipping management. It includes detailed analysis of both merchant and customer interfaces, database operations, and automated fulfillment processes.
Overview: Order Lifecycle Management
The marketplace implements a comprehensive order management system with dual interfaces for merchants and customers, supporting complete order tracking from placement to fulfillment with automated inventory management and payment processing.
Order States and Transitions
graph LR
A[Order Created] --> B[Invoice Generated]
B --> C[Payment Request Sent]
C --> D{Payment Status}
D -->|Paid| E[Payment Confirmed]
D -->|Unpaid| F[Invoice Expired/Reissued]
F --> C
E --> G[Inventory Updated]
G --> H{Fulfillment}
H -->|Ready| I[Shipped Status]
H -->|Issue| J[Error/Refund]
I --> K[Order Complete]
Core Order Data Models
1. Order Schema (models.py:400-485)
Order Structure Hierarchy
class OrderItem(BaseModel):
product_id: str # Product identifier from order
quantity: int # Quantity ordered
class OrderContact(BaseModel):
nostr: Optional[str] = None # Customer's nostr pubkey
phone: Optional[str] = None # Customer phone number
email: Optional[str] = None # Customer email address
class OrderExtra(BaseModel):
products: List[ProductOverview] # Snapshot of products at time of order
currency: str # Pricing currency (USD, EUR, sat, etc.)
btc_price: str # Exchange rate at time of order
shipping_cost: float = 0 # Shipping cost in currency
shipping_cost_sat: float = 0 # Shipping cost in satoshis
fail_message: Optional[str] = None # Error message if order failed
Complete Order Model
class Order(PartialOrder):
stall_id: str # Associated stall identifier
invoice_id: str # Lightning invoice payment hash
total: float # Total amount in satoshis
paid: bool = False # Payment status
shipped: bool = False # Shipping/fulfillment status
time: Optional[int] = None # Completion timestamp
extra: OrderExtra # Additional order metadata
2. Order Status Models (models.py:467-485)
Status Update Structure
class OrderStatusUpdate(BaseModel):
id: str # Order identifier
message: Optional[str] = None # Status update message
paid: Optional[bool] = False # Payment status
shipped: Optional[bool] = None # Shipping status
class OrderReissue(BaseModel):
id: str # Order identifier to reissue
shipping_id: Optional[str] = None # Updated shipping zone
class PaymentRequest(BaseModel):
id: str # Order identifier
message: Optional[str] = None # Response message
payment_options: List[PaymentOption] # Available payment methods
3. Database Schema (migrations.py:110-130)
Order Table Structure
CREATE TABLE nostrmarket.orders (
merchant_id TEXT NOT NULL, -- Merchant who owns this order
id TEXT PRIMARY KEY, -- Unique order identifier (UUID)
event_id TEXT, -- Nostr event ID for order placement
event_created_at INTEGER NOT NULL, -- Unix timestamp of order creation
public_key TEXT NOT NULL, -- Customer's public key
merchant_public_key TEXT NOT NULL, -- Merchant's public key
contact_data TEXT NOT NULL DEFAULT '{}', -- JSON contact information
extra_data TEXT NOT NULL DEFAULT '{}', -- JSON extra metadata
order_items TEXT NOT NULL, -- JSON array of ordered items
address TEXT, -- Shipping address (deprecated)
total REAL NOT NULL, -- Total amount in satoshis
shipping_id TEXT NOT NULL, -- Shipping zone identifier
stall_id TEXT NOT NULL, -- Associated stall identifier
invoice_id TEXT NOT NULL, -- Lightning invoice payment hash
paid BOOLEAN NOT NULL DEFAULT false, -- Payment confirmation
shipped BOOLEAN NOT NULL DEFAULT false, -- Fulfillment status
time INTEGER -- Completion timestamp
);
Merchant Order Management Interface
1. Order List Component (order-list.js)
Component Structure and Properties
window.app.component('order-list', {
name: 'order-list',
props: ['stall-id', 'customer-pubkey-filter', 'adminkey', 'inkey'],
template: '#order-list',
delimiters: ['${', '}'],
Advanced Search and Filtering (order-list.js:15-49)
data: function () {
return {
orders: [],
selectedOrder: null,
search: {
publicKey: null, // Filter by customer public key
isPaid: {label: 'All', id: null}, // Payment status filter
isShipped: {label: 'All', id: null}, // Shipping status filter
},
ternaryOptions: [
{label: 'All', id: null}, // Show all orders
{label: 'Yes', id: 'true'}, // Filter for paid/shipped = true
{label: 'No', id: 'false'} // Filter for paid/shipped = false
]
}
}
Dynamic Order Fetching (order-list.js:156-181)
getOrders: async function () {
try {
// Support both stall-specific and merchant-wide queries
const ordersPath = this.stallId
? `stall/order/${this.stallId}` // Orders for specific stall
: 'order' // All orders for merchant
// Build query parameters for filtering
const query = []
if (this.search.publicKey) {
query.push(`pubkey=${this.search.publicKey}`)
}
if (this.search.isPaid.id) {
query.push(`paid=${this.search.isPaid.id}`)
}
if (this.search.isShipped.id) {
query.push(`shipped=${this.search.isShipped.id}`)
}
const {data} = await LNbits.api.request(
'GET',
`/nostrmarket/api/v1/${ordersPath}?${query.join('&')}`,
this.inkey
)
this.orders = data.map(s => ({...s, expanded: false}))
} catch (error) {
LNbits.utils.notifyApiError(error)
}
}
2. Order Display and Calculations (order-list.js:119-155)
Product Information Retrieval
productName: function (order, productId) {
product = order.extra.products.find(p => p.id === productId)
if (product) {
return product.name
}
return ''
},
productPrice: function (order, productId) {
product = order.extra.products.find(p => p.id === productId)
if (product) {
return `${product.price} ${order.extra.currency}`
}
return ''
},
orderTotal: function (order) {
// Calculate total from individual product costs + shipping
const productCost = order.items.reduce((t, item) => {
product = order.extra.products.find(p => p.id === item.product_id)
return t + item.quantity * product.price
}, 0)
return productCost + order.extra.shipping_cost
}
3. Shipping Status Management (order-list.js:259-280)
Shipping Status Updates
updateOrderShipped: async function () {
this.selectedOrder.shipped = !this.selectedOrder.shipped
try {
await LNbits.api.request(
'PATCH',
`/nostrmarket/api/v1/order/${this.selectedOrder.id}`,
this.adminkey,
{
id: this.selectedOrder.id,
message: this.shippingMessage, // Custom message to customer
shipped: this.selectedOrder.shipped // New shipping status
}
)
this.$q.notify({
type: 'positive',
message: 'Order updated!'
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
this.showShipDialog = false
}
Shipping Dialog Interface (order-list.js:356-365)
showShipOrderDialog: function (order) {
this.selectedOrder = order
this.shippingMessage = order.shipped
? 'The order has been shipped!'
: 'The order has NOT yet been shipped!'
// Toggle status (will be confirmed on dialog submit)
this.selectedOrder.shipped = !order.shipped
this.showShipDialog = true
}
4. Order Recovery and Restoration (order-list.js:194-233)
Individual Order Restoration
restoreOrder: async function (eventId) {
try {
this.search.restoring = true
const {data} = await LNbits.api.request(
'PUT',
`/nostrmarket/api/v1/order/restore/${eventId}`, // Restore from DM event
this.adminkey
)
await this.getOrders() // Refresh order list
this.$q.notify({
type: 'positive',
message: 'Order restored!'
})
return data
} catch (error) {
LNbits.utils.notifyApiError(error)
} finally {
this.search.restoring = false
}
}
Bulk Order Restoration
restoreOrders: async function () {
try {
this.search.restoring = true
await LNbits.api.request(
'PUT',
`/nostrmarket/api/v1/orders/restore`, // Restore all from DMs
this.adminkey
)
await this.getOrders()
this.$q.notify({
type: 'positive',
message: 'Orders restored!'
})
} catch (error) {
LNbits.utils.notifyApiError(error)
}
}
5. Invoice Management (order-list.js:234-258)
Invoice Reissuance
reissueOrderInvoice: async function (order) {
try {
const {data} = await LNbits.api.request(
'PUT',
`/nostrmarket/api/v1/order/reissue`,
this.adminkey,
{
id: order.id,
shipping_id: order.shipping_id // Optional shipping zone update
}
)
this.$q.notify({
type: 'positive',
message: 'Order invoice reissued!'
})
// Update order in local state
data.expanded = order.expanded
const i = this.orders.map(o => o.id).indexOf(order.id)
if (i !== -1) {
this.orders[i] = {...this.orders[i], ...data}
}
} catch (error) {
LNbits.utils.notifyApiError(error)
}
}
Customer Order Interface
1. Customer Orders Component (CustomerOrders.vue)
Order Display Structure
<div v-for="merchant in merchantOrders" :key="merchant.id">
<q-card bordered class="q-mb-md">
<q-item>
<user-profile <!-- Merchant identity -->
:pubkey="merchant.pubkey"
:profiles="profiles"
></user-profile>
</q-item>
<q-list>
<div v-for="order in merchant.orders" :key="order.id">
<q-expansion-item dense expand-separator>
<template v-slot:header>
<q-item-section>
<q-item-label>
<strong><span v-text="order.stallName"></span></strong>
<q-badge <!-- Total amount -->
v-if="order.invoice?.human_readable_part?.amount"
color="orange"
>
<span v-text="formatCurrency(order.invoice.human_readable_part.amount / 1000, 'sat')"></span>
</q-badge>
</q-item-label>
</q-item-section>
<q-item-section side>
<q-badge :color="order.paid ? 'green' : 'grey'"> <!-- Payment status -->
<span v-text="order.paid ? 'Paid' : 'Not Paid'"></span>
</q-badge>
<q-badge :color="order.shipped ? 'green' : 'grey'"> <!-- Shipping status -->
<span v-text="order.shipped ? 'Shipped' : 'Not Shipped'"></span>
</q-badge>
</q-item-section>
</template>
</q-expansion-item>
</div>
</q-list>
</q-card>
</div>
2. Order Data Enrichment (CustomerOrders.vue:208-220)
Order Enhancement Pipeline
enrichOrder: function (order) {
const stall = this.stallForOrder(order);
return {
...order,
stallName: stall?.name || "Stall", // Stall name for display
shippingZone: stall?.shipping?.find( // Shipping zone details
(s) => s.id === order.shipping_id
) || { id: order.shipping_id, name: order.shipping_id },
invoice: this.invoiceForOrder(order), // Parsed Lightning invoice
products: this.getProductsForOrder(order), // Product details with quantities
};
}
Stall Association (CustomerOrders.vue:221-233)
stallForOrder: function (order) {
try {
const productId = order.items && order.items[0]?.product_id;
if (!productId) return;
const product = this.products.find((p) => p.id === productId);
if (!product) return;
const stall = this.stalls.find((s) => s.id === product.stall_id);
if (!stall) return;
return stall;
} catch (error) {
console.log(error);
}
}
3. Invoice Processing (CustomerOrders.vue:234-244)
Lightning Invoice Decoding
invoiceForOrder: function (order) {
try {
const lnPaymentOption = order?.payment_options?.find(
(p) => p.type === "ln" // Find Lightning payment option
);
if (!lnPaymentOption?.link) return;
return decode(lnPaymentOption.link); // Decode BOLT11 invoice
} catch (error) {
console.warn(error);
}
}
4. Product Aggregation (CustomerOrders.vue:246-259)
Order Item Processing
getProductsForOrder: function (order) {
if (!order?.items?.length) return [];
return order.items.map((i) => {
const product = this.products.find((p) => p.id === i.product_id) || {
id: i.product_id,
name: i.product_id, // Fallback if product not found
};
return {
...product,
orderedQuantity: i.quantity, // Add ordered quantity to product
};
});
}
Backend Order Operations
1. Order Creation (services.py:84-133)
Order Build Pipeline
async def build_order_with_payment(merchant_id, merchant_public_key, data):
# 1. Validate products and calculate costs
products = await get_products_by_ids(merchant_id, [p.product_id for p in data.items])
data.validate_order_items(products) # Ensure products exist and have stock
shipping_zone = await get_zone(merchant_id, data.shipping_id)
product_cost_sat, shipping_cost_sat = await data.costs_in_sats(
products, shipping_zone.id, shipping_zone.cost
)
# 2. Check inventory availability
success, _, message = await compute_products_new_quantity(
merchant_id, [i.product_id for i in data.items], data.items
)
if not success:
raise ValueError(message) # Insufficient inventory
# 3. Create Lightning invoice via LNbits
payment = await create_invoice(
wallet_id=wallet_id,
amount=round(product_cost_sat + shipping_cost_sat),
memo=f"Order '{data.id}' for pubkey '{data.public_key}'",
extra={
"tag": "nostrmarket", # Tags invoice as marketplace
"order_id": data.id,
"merchant_pubkey": merchant_public_key,
},
)
# 4. Create order record
order = Order(
**data.dict(),
stall_id=products[0].stall_id,
invoice_id=payment.payment_hash,
total=product_cost_sat + shipping_cost_sat,
extra=extra,
)
return order, payment.bolt11, receipt
2. Order Retrieval API (views_api.py:540-577)
Multi-filter Order Queries
@nostrmarket_ext.get("/api/v1/stall/order/{stall_id}")
async def api_get_orders_for_stall(
stall_id: str,
paid: Optional[bool] = None, # Filter by payment status
shipped: Optional[bool] = None, # Filter by shipping status
pubkey: Optional[str] = None, # Filter by customer pubkey
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> List[Order]:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
orders = await get_orders_for_stall(
merchant.id, stall_id, paid=paid, shipped=shipped, public_key=pubkey
)
return orders
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail=str(ex)
) from ex
3. Order Status Updates (views_api.py:625-641)
Shipping Status API
@nostrmarket_ext.patch("/api/v1/order/{order_id}")
async def api_update_order_status(
data: OrderStatusUpdate,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Order:
try:
assert data.shipped is not None, "Shipped value is required for order"
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found for order {data.id}"
# Update shipping status in database
order = await update_order_shipped_status(merchant.id, data.id, data.shipped)
assert order, "Cannot find updated order"
# Send status update to customer via DM
data.paid = order.paid # Include current payment status
dm_content = json.dumps(
{"type": DirectMessageType.ORDER_PAID_OR_SHIPPED.value, **data.dict()},
separators=(",", ":"),
ensure_ascii=False,
)
await reply_to_structured_dm(
merchant, order.public_key, DirectMessageType.ORDER_PAID_OR_SHIPPED.value, dm_content
)
return order
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail=str(ex)
) from ex
4. Invoice Reissuance (views_api.py:710-740)
Payment Request Regeneration
@nostrmarket_ext.put("/api/v1/order/reissue")
async def api_reissue_order_invoice(
reissue_data: OrderReissue,
wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Order:
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
# Get existing order
data = await get_order(merchant.id, reissue_data.id)
assert data, "Order cannot be found"
# Update shipping zone if provided
if reissue_data.shipping_id:
data.shipping_id = reissue_data.shipping_id
# Generate new payment request
payment_req, order = await build_order_with_payment(
merchant.id, merchant.public_key, data
)
# Update order with new invoice details
order_update = {
"total": payment_req.total,
"invoice_id": order.invoice_id, # New payment hash
"extra_data": json.dumps(order.extra.dict()),
}
await update_order(
merchant.id,
order.id,
**order_update,
)
# Send new payment request to customer
payment_req = PaymentRequest(
id=order.id,
message="Updated payment request",
payment_options=[PaymentOption(type="ln", link=order.bolt11)],
)
dm_content = json.dumps(
{"type": DirectMessageType.PAYMENT_REQUEST.value, **payment_req.dict()},
)
await reply_to_structured_dm(
merchant, order.public_key, DirectMessageType.PAYMENT_REQUEST.value, dm_content
)
return await get_order(merchant.id, reissue_data.id)
except Exception as ex:
logger.warning(ex)
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail="Cannot reissue order invoice",
) from ex
Order Restoration System
1. Order Recovery from Direct Messages (services.py:645-690)
DM-based Order Restoration
async def create_or_update_order_from_dm(
merchant_id: str, merchant_pubkey: str, dm: DirectMessage
):
type_, json_data = PartialDirectMessage.parse_message(dm.message)
if not json_data or "id" not in json_data:
return
if type_ == DirectMessageType.CUSTOMER_ORDER:
# Restore customer order from DM
order, _ = await extract_customer_order_from_dm(
merchant_id, merchant_pubkey, dm, json_data
)
new_order = await create_order(merchant_id, order)
# Handle stall association updates
if new_order.stall_id == "None" and order.stall_id != "None":
await update_order(
merchant_id,
order.id,
**{
"stall_id": order.stall_id,
"extra_data": json.dumps(order.extra.dict()),
},
)
return
if type_ == DirectMessageType.PAYMENT_REQUEST:
# Update order with payment request details
payment_request = PaymentRequest(**json_data)
pr = payment_request.payment_options[0].link
invoice = decode(pr)
total = invoice.amount_msat / 1000 if invoice.amount_msat else 0
await update_order(
merchant_id,
payment_request.id,
**{"total": total, "invoice_id": invoice.payment_hash},
)
return
if type_ == DirectMessageType.ORDER_PAID_OR_SHIPPED:
# Update order status from status messages
order_update = OrderStatusUpdate(**json_data)
if order_update.paid:
await update_order_paid_status(order_update.id, True)
if order_update.shipped:
await update_order_shipped_status(merchant_id, order_update.id, True)
2. Bulk Restoration API (views_api.py:580-595)
Complete Order Recovery
@nostrmarket_ext.put("/api/v1/orders/restore")
async def api_restore_orders_from_dms(
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant, "Merchant cannot be found"
# Get all order-related direct messages
dms = await get_orders_from_direct_messages(merchant.id)
for dm in dms:
try:
# Attempt to restore/update each order from DM history
await create_or_update_order_from_dm(
merchant.id, merchant.public_key, dm
)
except Exception as e:
logger.debug(
f"Failed to restore order from event '{dm.event_id}': '{e!s}'."
)
continue
return {"status": "Orders restoration completed!"}
except AssertionError as ex:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail=str(ex)
) from ex
Real-time Order Updates
1. WebSocket Order Notifications (order-list.js:281-296)
Live Order Addition
addOrder: async function (data) {
if (
!this.search.publicKey ||
this.search.publicKey === data.customerPubkey // Filter matches current view
) {
const orderData = JSON.parse(data.dm.message)
const i = this.orders.map(o => o.id).indexOf(orderData.id)
if (i === -1) { // Prevent duplicates
const order = await this.getOrder(orderData.id) // Fetch complete order data
this.orders.unshift(order) // Add to top of list
}
}
}
2. Payment Status Updates (order-list.js:391-396)
Real-time Payment Confirmation
orderPaid: function (orderId) {
const order = this.orders.find(o => o.id === orderId)
if (order) {
order.paid = true // Update payment status immediately
}
}
Advanced Order Management Features
1. Order Selection and Deep Linking (order-list.js:294-315)
Order Detail Navigation
orderSelected: async function (orderId, eventId) {
const order = await this.getOrder(orderId)
if (!order) {
// Order missing - offer restoration from DM
LNbits.utils
.confirmDialog(
'Order could not be found. Do you want to restore it from this direct message?'
)
.onOk(async () => {
const restoredOrder = await this.restoreOrder(eventId)
if (restoredOrder) {
restoredOrder.expanded = true
restoredOrder.isNew = false
this.orders = [restoredOrder] // Show only restored order
}
})
return
}
// Show order details
order.expanded = true
order.isNew = false
this.orders = [order] // Focus on single order
}
2. Customer Association and Filtering
Customer Management Integration
computed: {
customerOptions: function () {
const options = this.customers.map(c => ({
label: this.buildCustomerLabel(c), // Include unread message counts
value: c.public_key
}))
options.unshift({label: 'All', value: null, id: null}) // All customers option
return options
}
}
3. Shipping Zone Integration (order-list.js:348-355)
Dynamic Shipping Options
getStallZones: function (stallId) {
const stall = this.stalls.find(s => s.id === stallId)
if (!stall) return []
return this.zoneOptions.filter(z =>
stall.shipping_zones.find(s => s.id === z.id) // Only zones supported by stall
)
}
Database Operations
1. Order CRUD Operations (crud.py)
Order Creation
async def create_order(merchant_id: str, o: Order) -> Order:
await db.execute(
"""
INSERT INTO nostrmarket.orders (
merchant_id, id, event_id, event_created_at, public_key,
merchant_public_key, contact_data, extra_data, order_items,
address, total, shipping_id, stall_id, invoice_id, paid, shipped
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
merchant_id, o.id, o.event_id, o.event_created_at, o.public_key,
o.merchant_public_key, json.dumps(o.contact.dict()),
json.dumps(o.extra.dict()), json.dumps([i.dict() for i in o.items]),
o.address, o.total, o.shipping_id, o.stall_id, o.invoice_id,
o.paid, o.shipped,
),
)
return o
Flexible Order Queries
async def get_orders(merchant_id: str, **kwargs) -> List[Order]:
# Build dynamic WHERE clause from keyword arguments
q = " AND ".join(
[
f"{field[0]} = :{field[0]}"
for field in kwargs.items()
if field[1] is not None
]
)
rows: list[dict] = await db.fetchall(
f"SELECT * FROM nostrmarket.orders WHERE merchant_id = :merchant_id "
f"{' AND ' + q if q else ''} ORDER BY event_created_at DESC",
{"merchant_id": merchant_id, **kwargs},
)
return [Order.from_row(row) for row in rows]
2. Status Update Operations
Payment Status Updates
async def update_order_paid_status(order_id: str, paid: bool) -> Optional[Order]:
await db.execute(
"UPDATE nostrmarket.orders SET paid = :paid WHERE id = :id",
{"paid": paid, "id": order_id},
)
row: dict = await db.fetchone(
"SELECT * FROM nostrmarket.orders WHERE id = :id", {"id": order_id}
)
return Order.from_row(row) if row else None
Shipping Status Updates
async def update_order_shipped_status(
merchant_id: str, order_id: str, shipped: bool
) -> Optional[Order]:
await db.execute(
"""
UPDATE nostrmarket.orders
SET shipped = :shipped
WHERE merchant_id = :merchant_id AND id = :id
""",
{"shipped": shipped, "merchant_id": merchant_id, "id": order_id},
)
row: dict = await db.fetchone(
"SELECT * FROM nostrmarket.orders WHERE merchant_id = :merchant_id AND id = :id",
{"merchant_id": merchant_id, "id": order_id},
)
return Order.from_row(row) if row else None
Error Handling and Edge Cases
1. Order Restoration Failures
- Missing Products: Orders reference products that no longer exist
- Invalid Stall Association: Product moved between stalls after order creation
- Corrupted DM Data: JSON parsing errors in message restoration
- Payment Hash Conflicts: Duplicate invoice IDs from reissuance
2. Payment Processing Issues
- Invoice Expiration: Lightning invoices expire after timeout
- Partial Payments: Underpayment or overpayment scenarios
- Payment Verification: Webhook delays or failures
- Double Payment: Multiple payments for same order
3. Inventory Synchronization
- Race Conditions: Multiple orders for limited stock
- Negative Inventory: Orders processed despite insufficient stock
- Product Updates: Price or availability changes after order placement
- Stall Deactivation: Orders for disabled stalls or products
Integration Points
1. Payment System Integration
- LNbits Invoice Creation: Automatic Lightning invoice generation
- Payment Monitoring: Real-time payment confirmation via webhooks
- Refund Processing: Automated refunds for failed orders
- Multi-currency Support: Fiat pricing with BTC conversion
2. Inventory Management Integration
- Stock Validation: Pre-order inventory checking
- Automatic Deduction: Post-payment inventory updates
- Backorder Handling: Out-of-stock order management
- Restock Notifications: Customer alerts for inventory replenishment
3. Communication System Integration
- Status Updates: Automated customer notifications
- Order Confirmations: Receipt and tracking information
- Shipping Notifications: Fulfillment status updates
- Support Integration: Customer service ticket creation
This comprehensive order management system provides complete lifecycle tracking from initial order placement through final fulfillment, with robust error handling, real-time updates, and flexible merchant tools for efficient order processing and customer communication.