Auto-create + publish default stall, republish stall on product publish
Two complementary fixes for the "Unknown Stall" bug, where a customer sees a product on the relay but the parent stall is missing. 1. _auto_create_merchant() now creates a default "<username>'s Store" stall and publishes its kind 30017 event before returning. New users land with a fully-published merchant identity, so the very first product they create has a known parent stall on relays. 2. POST /api/v1/product (api_create_product) now republishes the parent stall before publishing the product. NIP-33 parameterized replaceable events make this idempotent, but it self-heals every existing case where the stall publish failed or never happened (transient relay issues, accounts that pre-date the auto-publish flow, manual stall creation that didn't reach all relays). This complements the LNbits-side fix in core/services/users.py (_create_default_merchant publishes the stall on signup) and the webapp self-heal in useMarketStallSelfHeal.ts. With all three layers, "Unknown Stall" should disappear from the customer view. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3cc798aab2
commit
e481c9179d
1 changed files with 49 additions and 9 deletions
58
views_api.py
58
views_api.py
|
|
@ -101,6 +101,10 @@ async def _auto_create_merchant(
|
||||||
"""
|
"""
|
||||||
Provision a merchant record from the user's account keypair.
|
Provision a merchant record from the user's account keypair.
|
||||||
Called automatically on first GET or explicitly via POST.
|
Called automatically on first GET or explicitly via POST.
|
||||||
|
|
||||||
|
Also creates a default "Online" shipping zone and a default stall named
|
||||||
|
after the user, then publishes the stall to relays so that any product
|
||||||
|
the user creates references a stall the customer-facing client can find.
|
||||||
"""
|
"""
|
||||||
account = await get_account(wallet.wallet.user)
|
account = await get_account(wallet.wallet.user)
|
||||||
assert account, "User account not found"
|
assert account, "User account not found"
|
||||||
|
|
@ -127,16 +131,37 @@ async def _auto_create_merchant(
|
||||||
|
|
||||||
merchant = await create_merchant(wallet.wallet.user, partial_merchant)
|
merchant = await create_merchant(wallet.wallet.user, partial_merchant)
|
||||||
|
|
||||||
await create_zone(
|
online_zone = Zone(
|
||||||
merchant.id,
|
id=f"online-{merchant.public_key}",
|
||||||
Zone(
|
name="Online",
|
||||||
id=f"online-{merchant.public_key}",
|
currency="sat",
|
||||||
name="Online",
|
cost=0,
|
||||||
currency="sat",
|
countries=["Free (digital)"],
|
||||||
cost=0,
|
|
||||||
countries=["Free (digital)"],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
await create_zone(merchant.id, online_zone)
|
||||||
|
|
||||||
|
# Create + publish a default stall so products created through the UI
|
||||||
|
# always have a published parent. Without this, a product publish lands
|
||||||
|
# on relays referencing a stall_id that no relay has seen, and the
|
||||||
|
# customer client renders "Unknown Stall".
|
||||||
|
display_name = account.username or "My"
|
||||||
|
default_stall = Stall(
|
||||||
|
wallet=wallet.wallet.id,
|
||||||
|
name=f"{display_name}'s Store",
|
||||||
|
currency="sat",
|
||||||
|
shipping_zones=[online_zone],
|
||||||
|
)
|
||||||
|
default_stall = await create_stall(merchant.id, default_stall)
|
||||||
|
try:
|
||||||
|
stall_event = await sign_and_send_to_nostr(merchant, default_stall)
|
||||||
|
default_stall.event_id = stall_event.id
|
||||||
|
await update_stall(merchant.id, default_stall)
|
||||||
|
except Exception as ex:
|
||||||
|
# Non-fatal: merchant is usable; a product publish (or self-heal)
|
||||||
|
# will republish the stall later.
|
||||||
|
logger.warning(
|
||||||
|
f"[NOSTRMARKET] Failed to publish default stall for {merchant.id}: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
await resubscribe_to_all_merchants()
|
await resubscribe_to_all_merchants()
|
||||||
await nostr_client.merchant_temp_subscription(public_key)
|
await nostr_client.merchant_temp_subscription(public_key)
|
||||||
|
|
@ -767,6 +792,21 @@ async def api_create_product(
|
||||||
assert stall, "Stall missing for product"
|
assert stall, "Stall missing for product"
|
||||||
data.config.currency = stall.currency
|
data.config.currency = stall.currency
|
||||||
|
|
||||||
|
# Re-publish the parent stall before publishing the product. NIP-33
|
||||||
|
# parameterized replaceable events make this idempotent on relays.
|
||||||
|
# This guarantees the customer client never sees a product whose
|
||||||
|
# parent stall isn't on the relay (e.g., when the original stall
|
||||||
|
# publish failed transiently or never ran).
|
||||||
|
try:
|
||||||
|
stall_event = await sign_and_send_to_nostr(merchant, stall)
|
||||||
|
stall.event_id = stall_event.id
|
||||||
|
await update_stall(merchant.id, stall)
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(
|
||||||
|
f"[NOSTRMARKET] Failed to refresh stall {stall.id} "
|
||||||
|
f"before product publish: {ex}"
|
||||||
|
)
|
||||||
|
|
||||||
product = await create_product(merchant.id, data=data)
|
product = await create_product(merchant.id, data=data)
|
||||||
|
|
||||||
event = await sign_and_send_to_nostr(merchant, product)
|
event = await sign_and_send_to_nostr(merchant, product)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue