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 committed by Patrick Mulligan
parent 7c7d6c7953
commit 7245123e49
3 changed files with 56 additions and 46 deletions

View file

@ -49,6 +49,10 @@ class MerchantConfig(MerchantProfile):
restore_in_progress: bool | None = False restore_in_progress: bool | None = 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

@ -49,46 +49,19 @@ window.app = Vue.createApp({
} }
}, },
methods: { methods: {
generateKeys: function () { generateKeys: async function () {
const privateKey = nostr.generatePrivateKey() // No longer need to generate keys here - the backend will use user's existing keypairs
const publicKey = nostr.getPublicKey(privateKey) await this.createMerchant()
this.generateKeyDialog.privateKey = privateKey
this.generateKeyDialog.nsec = nostr.nip19.nsecEncode(privateKey)
this.generateKeyDialog.npub = nostr.nip19.npubEncode(publicKey)
this.generateKeyDialog.showNsec = false
this.generateKeyDialog.show = true
},
confirmGenerateKey: async function () {
this.generateKeyDialog.show = false
await this.createMerchant(this.generateKeyDialog.privateKey)
}, },
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
}
try {
if (privateKey.toLowerCase().startsWith('nsec')) {
privateKey = nostr.nip19.decode(privateKey).data
}
// Check if this key is already in use
const publicKey = nostr.getPublicKey(privateKey)
if (this.merchant?.public_key === publicKey) {
this.$q.notify({ this.$q.notify({
type: 'warning', type: 'info',
message: 'This key is already your current profile' message: 'Merchants now use your account Nostr keys automatically. Key import is no longer needed.',
timeout: 3000
}) })
return
}
} catch (error) {
this.$q.notify({
type: 'negative',
message: `${error}`
})
return
}
await this.createMerchant(privateKey)
}, },
showImportKeysDialog: async function () { showImportKeysDialog: async function () {
this.importKeyDialog.show = true this.importKeyDialog.show = true
@ -143,12 +116,9 @@ window.app = Vue.createApp({
this.showKeys = false this.showKeys = false
this.stallCount = 0 this.stallCount = 0
}, },
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,12 +4,14 @@ from http import HTTPStatus
from fastapi import Depends from fastapi import Depends
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from lnbits.core.models import WalletTypeInfo from lnbits.core.models import WalletTypeInfo
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 (
require_admin_key, require_admin_key,
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
@ -58,6 +60,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,15 +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)
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,
@ -113,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: