From 0b7639adf5131d483ff2289779c35da4edebfd14 Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 7 Sep 2025 03:25:19 +0200 Subject: [PATCH] Improve merchant creation with automatic keypair generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enhance the merchant creation process by automatically generating Nostr keypairs for users who don't have them, and streamline the API interface. Changes: - Add CreateMerchantRequest model to simplify merchant creation API - Auto-generate Nostr keypairs for users without existing keys - Update merchant creation endpoint to use user account keypairs - Improve error handling and validation in merchant creation flow - Clean up frontend JavaScript for merchant creation This ensures all merchants have proper Nostr keypairs for marketplace functionality without requiring manual key management from users. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- models.py | 4 ++++ static/js/index.js | 31 ++++++++++--------------------- views_api.py | 45 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/models.py b/models.py index 1faab7c..1abe075 100644 --- a/models.py +++ b/models.py @@ -44,6 +44,10 @@ class MerchantConfig(MerchantProfile): restore_in_progress: Optional[bool] = False +class CreateMerchantRequest(BaseModel): + config: MerchantConfig = MerchantConfig() + + class PartialMerchant(BaseModel): private_key: str public_key: str diff --git a/static/js/index.js b/static/js/index.js index 3d89779..0d01f82 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -21,26 +21,18 @@ window.app = Vue.createApp({ }, methods: { generateKeys: async function () { - const privateKey = nostr.generatePrivateKey() - await this.createMerchant(privateKey) + // No longer need to generate keys here - the backend will use user's existing keypairs + await this.createMerchant() }, importKeys: async function () { this.importKeyDialog.show = false - let privateKey = this.importKeyDialog.data.privateKey - if (!privateKey) { - return - } - try { - if (privateKey.toLowerCase().startsWith('nsec')) { - privateKey = nostr.nip19.decode(privateKey).data - } - } catch (error) { - this.$q.notify({ - type: 'negative', - message: `${error}` - }) - } - await this.createMerchant(privateKey) + // Import keys functionality removed since we use user's native keypairs + // Show a message that this is no longer needed + this.$q.notify({ + type: 'info', + message: 'Merchants now use your account Nostr keys automatically. Key import is no longer needed.', + timeout: 3000 + }) }, showImportKeysDialog: async function () { this.importKeyDialog.show = true @@ -94,12 +86,9 @@ window.app = Vue.createApp({ this.activeChatCustomer = '' this.showKeys = false }, - createMerchant: async function (privateKey) { + createMerchant: async function () { try { - const pubkey = nostr.getPublicKey(privateKey) const payload = { - private_key: privateKey, - public_key: pubkey, config: {} } const {data} = await LNbits.api.request( diff --git a/views_api.py b/views_api.py index b8019fa..f858397 100644 --- a/views_api.py +++ b/views_api.py @@ -4,6 +4,7 @@ from typing import List, Optional from fastapi import Depends from fastapi.exceptions import HTTPException +from lnbits.core.crud import get_account, update_account from lnbits.core.services import websocket_updater from lnbits.decorators import ( WalletTypeInfo, @@ -11,6 +12,7 @@ from lnbits.decorators import ( require_invoice_key, ) from lnbits.utils.exchange_rates import currencies +from lnbits.utils.nostr import generate_keypair from loguru import logger from . import nostr_client, nostrmarket_ext @@ -59,6 +61,7 @@ from .crud import ( ) from .helpers import normalize_public_key from .models import ( + CreateMerchantRequest, Customer, DirectMessage, DirectMessageType, @@ -90,18 +93,48 @@ from .services import ( @nostrmarket_ext.post("/api/v1/merchant") async def api_create_merchant( - data: PartialMerchant, + data: CreateMerchantRequest, wallet: WalletTypeInfo = Depends(require_admin_key), ) -> Merchant: try: - merchant = await get_merchant_by_pubkey(data.public_key) - assert merchant is None, "A merchant already uses this public key" - + # Check if merchant already exists for this user merchant = await get_merchant_for_user(wallet.wallet.user) assert merchant is None, "A merchant already exists for this user" - merchant = await create_merchant(wallet.wallet.user, data) + # Get user's account to access their Nostr keypairs + account = await get_account(wallet.wallet.user) + if not account: + raise HTTPException( + status_code=HTTPStatus.NOT_FOUND, + detail="User account not found", + ) + + # Check if user has Nostr keypairs, generate them if not + if not account.pubkey or not account.prvkey: + # Generate new keypair for user + private_key, public_key = generate_keypair() + + # Update user account with new keypairs + account.pubkey = public_key + account.prvkey = private_key + await update_account(account) + else: + public_key = account.pubkey + private_key = account.prvkey + + # Check if another merchant is already using this public key + existing_merchant = await get_merchant_by_pubkey(public_key) + assert existing_merchant is None, "A merchant already uses this public key" + + # Create PartialMerchant with user's keypairs + partial_merchant = PartialMerchant( + private_key=private_key, + public_key=public_key, + config=data.config + ) + + merchant = await create_merchant(wallet.wallet.user, partial_merchant) await create_zone( merchant.id, @@ -116,7 +149,7 @@ async def api_create_merchant( await resubscribe_to_all_merchants() - await nostr_client.merchant_temp_subscription(data.public_key) + await nostr_client.merchant_temp_subscription(public_key) return merchant except AssertionError as ex: