Improve merchant creation with automatic keypair generation

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 <noreply@anthropic.com>
This commit is contained in:
padreug 2025-09-07 03:25:19 +02:00
parent 4b65ed411a
commit 0b7639adf5
3 changed files with 53 additions and 27 deletions

View file

@ -44,6 +44,10 @@ class MerchantConfig(MerchantProfile):
restore_in_progress: Optional[bool] = False restore_in_progress: Optional[bool] = False
class CreateMerchantRequest(BaseModel):
config: MerchantConfig = MerchantConfig()
class PartialMerchant(BaseModel): class PartialMerchant(BaseModel):
private_key: str private_key: str
public_key: str public_key: str

View file

@ -21,26 +21,18 @@ window.app = Vue.createApp({
}, },
methods: { methods: {
generateKeys: async function () { generateKeys: async function () {
const privateKey = nostr.generatePrivateKey() // No longer need to generate keys here - the backend will use user's existing keypairs
await this.createMerchant(privateKey) await this.createMerchant()
}, },
importKeys: async function () { importKeys: async function () {
this.importKeyDialog.show = false this.importKeyDialog.show = false
let privateKey = this.importKeyDialog.data.privateKey // Import keys functionality removed since we use user's native keypairs
if (!privateKey) { // Show a message that this is no longer needed
return this.$q.notify({
} type: 'info',
try { message: 'Merchants now use your account Nostr keys automatically. Key import is no longer needed.',
if (privateKey.toLowerCase().startsWith('nsec')) { timeout: 3000
privateKey = nostr.nip19.decode(privateKey).data })
}
} catch (error) {
this.$q.notify({
type: 'negative',
message: `${error}`
})
}
await this.createMerchant(privateKey)
}, },
showImportKeysDialog: async function () { showImportKeysDialog: async function () {
this.importKeyDialog.show = true this.importKeyDialog.show = true
@ -94,12 +86,9 @@ window.app = Vue.createApp({
this.activeChatCustomer = '' this.activeChatCustomer = ''
this.showKeys = false this.showKeys = false
}, },
createMerchant: async function (privateKey) { createMerchant: async function () {
try { try {
const pubkey = nostr.getPublicKey(privateKey)
const payload = { const payload = {
private_key: privateKey,
public_key: pubkey,
config: {} config: {}
} }
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(

View file

@ -4,6 +4,7 @@ from typing import List, Optional
from fastapi import Depends from fastapi import Depends
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from lnbits.core.crud import get_account, update_account
from lnbits.core.services import websocket_updater from lnbits.core.services import websocket_updater
from lnbits.decorators import ( from lnbits.decorators import (
WalletTypeInfo, WalletTypeInfo,
@ -11,6 +12,7 @@ from lnbits.decorators import (
require_invoice_key, require_invoice_key,
) )
from lnbits.utils.exchange_rates import currencies from lnbits.utils.exchange_rates import currencies
from lnbits.utils.nostr import generate_keypair
from loguru import logger from loguru import logger
from . import nostr_client, nostrmarket_ext from . import nostr_client, nostrmarket_ext
@ -59,6 +61,7 @@ from .crud import (
) )
from .helpers import normalize_public_key from .helpers import normalize_public_key
from .models import ( from .models import (
CreateMerchantRequest,
Customer, Customer,
DirectMessage, DirectMessage,
DirectMessageType, DirectMessageType,
@ -90,18 +93,48 @@ from .services import (
@nostrmarket_ext.post("/api/v1/merchant") @nostrmarket_ext.post("/api/v1/merchant")
async def api_create_merchant( async def api_create_merchant(
data: PartialMerchant, data: CreateMerchantRequest,
wallet: WalletTypeInfo = Depends(require_admin_key), wallet: WalletTypeInfo = Depends(require_admin_key),
) -> Merchant: ) -> Merchant:
try: try:
merchant = await get_merchant_by_pubkey(data.public_key) # Check if merchant already exists for this user
assert merchant is None, "A merchant already uses this public key"
merchant = await get_merchant_for_user(wallet.wallet.user) merchant = await get_merchant_for_user(wallet.wallet.user)
assert merchant is None, "A merchant already exists for this 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( await create_zone(
merchant.id, merchant.id,
@ -116,7 +149,7 @@ async def api_create_merchant(
await resubscribe_to_all_merchants() await resubscribe_to_all_merchants()
await nostr_client.merchant_temp_subscription(data.public_key) await nostr_client.merchant_temp_subscription(public_key)
return merchant return merchant
except AssertionError as ex: except AssertionError as ex: