Extract provision_merchant() service for shared use
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:
parent
e481c9179d
commit
05ebf042ac
2 changed files with 86 additions and 52 deletions
71
services.py
71
services.py
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue