Extract provision_merchant() service for shared use
Some checks failed
ci.yml / Extract provision_merchant() service for shared use (pull_request) Failing after 0s
ci.yml / Extract provision_merchant() service for shared use (push) Failing after 0s

Both _auto_create_merchant (lazy GET fallback in views_api) and
LNbits' _create_default_merchant (eager signup hook) used to
reimplement merchant + zone + stall creation independently. Moves the
canonical implementation to services.provision_merchant() so both
call sites stay in lockstep — future changes (NIP-17 kind 10050 relay
list, additional default zones, etc.) only happen in one place.

- services.provision_merchant(user_id, wallet_id, public_key,
  private_key, display_name, config): creates merchant if absent,
  default 'Online' zone, default '<username>'s Store' stall, and
  publishes the kind 30017 stall event. Idempotent on the merchant
  pubkey: returns the existing merchant unchanged if one exists.
- views_api._auto_create_merchant: now a 10-line wrapper that loads
  the account, generates fallback keys if missing, then delegates.

The LNbits-side hook (lnbits/core/services/users.py:_create_default_merchant)
will be updated in a companion commit to also call this service.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-03 16:19:52 +02:00
commit 05ebf042ac
2 changed files with 86 additions and 52 deletions

View file

@ -12,9 +12,11 @@ from .crud import (
CustomerProfile,
create_customer,
create_direct_message,
create_merchant,
create_order,
create_product,
create_stall,
create_zone,
get_customer,
get_last_direct_messages_created_at,
get_last_product_update_time,
@ -42,6 +44,7 @@ from .models import (
DirectMessage,
DirectMessageType,
Merchant,
MerchantConfig,
Nostrable,
Order,
OrderContact,
@ -49,11 +52,13 @@ from .models import (
OrderItem,
OrderStatusUpdate,
PartialDirectMessage,
PartialMerchant,
PartialOrder,
PaymentOption,
PaymentRequest,
Product,
Stall,
Zone,
)
from .nostr.event import NostrEvent
from .nostr.nip59 import unwrap_message, wrap_message
@ -180,6 +185,72 @@ async def sign_and_send_to_nostr(
return event
async def provision_merchant(
user_id: str,
wallet_id: str,
public_key: str,
private_key: str,
display_name: Optional[str] = None,
config: Optional[MerchantConfig] = None,
) -> Merchant:
"""
Provision a merchant with a default shipping zone and default stall,
and publish the stall to Nostr relays.
Single source of truth used by:
- LNbits user-creation hook (eager, on signup) see
lnbits/core/services/users.py:_create_default_merchant
- nostrmarket views_api._auto_create_merchant (lazy, on first GET
/api/v1/merchant when a merchant is missing).
Idempotent on the merchant: if a merchant with this pubkey already
exists, returns it without recreating zone/stall.
"""
existing = await get_merchant_by_pubkey(public_key)
if existing:
return existing
partial_merchant = PartialMerchant(
private_key=private_key,
public_key=public_key,
config=config or MerchantConfig(),
)
merchant = await create_merchant(user_id, partial_merchant)
online_zone = Zone(
id=f"online-{merchant.public_key}",
name="Online",
currency="sat",
cost=0,
countries=["Free (digital)"],
)
await create_zone(merchant.id, online_zone)
name = display_name or "My"
default_stall = Stall(
wallet=wallet_id,
name=f"{name}'s Store",
currency="sat",
shipping_zones=[online_zone],
)
default_stall = await create_stall(merchant.id, default_stall)
# Publish the kind 30017 stall event so customers' clients can resolve
# the stall name when they fetch products. Non-fatal on failure: a
# later product publish (or webapp self-heal) will retry.
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:
logger.warning(
f"[NOSTRMARKET] Failed to publish default stall for "
f"merchant {merchant.id}: {ex}"
)
return merchant
async def handle_order_paid(order_id: str, merchant_pubkey: str):
try:
order = await update_order_paid_status(order_id, True)