feat(extensions): pay from caller's balance via PayAppUserInvoice
Some checks are pending
Docker Compose Actions Workflow / test (push) Waiting to run
Some checks are pending
Docker Compose Actions Workflow / test (push) Waiting to run
When userPubkey is provided, resolve the ApplicationUser and call applicationManager.PayAppUserInvoice instead of paymentManager.PayInvoice directly. This ensures notifyAppUserPayment fires, sending LiveUserOperation events via Nostr for real-time balance updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1aad229c19
commit
dcb7dca9b5
3 changed files with 50 additions and 7 deletions
|
|
@ -20,6 +20,17 @@ export interface MainHandlerInterface {
|
||||||
// Application management
|
// Application management
|
||||||
applicationManager: {
|
applicationManager: {
|
||||||
getById(id: string): Promise<any>
|
getById(id: string): Promise<any>
|
||||||
|
PayAppUserInvoice(appId: string, req: {
|
||||||
|
amount: number
|
||||||
|
invoice: string
|
||||||
|
user_identifier: string
|
||||||
|
debit_npub?: string
|
||||||
|
}): Promise<{
|
||||||
|
preimage: string
|
||||||
|
amount_paid: number
|
||||||
|
network_fee: number
|
||||||
|
service_fee: number
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Payment operations
|
// Payment operations
|
||||||
|
|
@ -41,6 +52,7 @@ export interface MainHandlerInterface {
|
||||||
applicationId: string
|
applicationId: string
|
||||||
paymentRequest: string
|
paymentRequest: string
|
||||||
maxFeeSats?: number
|
maxFeeSats?: number
|
||||||
|
userPubkey?: string
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
paymentHash: string
|
paymentHash: string
|
||||||
feeSats: number
|
feeSats: number
|
||||||
|
|
@ -156,16 +168,19 @@ export class ExtensionContextImpl implements ExtensionContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pay a Lightning invoice
|
* Pay a Lightning invoice
|
||||||
|
* If userPubkey is provided, pays from that user's balance instead of app.owner
|
||||||
*/
|
*/
|
||||||
async payInvoice(
|
async payInvoice(
|
||||||
applicationId: string,
|
applicationId: string,
|
||||||
paymentRequest: string,
|
paymentRequest: string,
|
||||||
maxFeeSats?: number
|
maxFeeSats?: number,
|
||||||
|
userPubkey?: string
|
||||||
): Promise<{ paymentHash: string; feeSats: number }> {
|
): Promise<{ paymentHash: string; feeSats: number }> {
|
||||||
return this.mainHandler.paymentManager.payInvoice({
|
return this.mainHandler.paymentManager.payInvoice({
|
||||||
applicationId,
|
applicationId,
|
||||||
paymentRequest,
|
paymentRequest,
|
||||||
maxFeeSats
|
maxFeeSats,
|
||||||
|
userPubkey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,10 @@ export function createMainHandlerAdapter(mainHandler: Main): MainHandlerInterfac
|
||||||
// GetApplication throws if not found
|
// GetApplication throws if not found
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async PayAppUserInvoice(appId, req) {
|
||||||
|
return mainHandler.applicationManager.PayAppUserInvoice(appId, req)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -73,6 +77,7 @@ export function createMainHandlerAdapter(mainHandler: Main): MainHandlerInterfac
|
||||||
applicationId: string
|
applicationId: string
|
||||||
paymentRequest: string
|
paymentRequest: string
|
||||||
maxFeeSats?: number
|
maxFeeSats?: number
|
||||||
|
userPubkey?: string
|
||||||
}) {
|
}) {
|
||||||
// Get the app to find the user ID and app reference
|
// Get the app to find the user ID and app reference
|
||||||
const app = await mainHandler.storage.applicationStorage.GetApplication(params.applicationId)
|
const app = await mainHandler.storage.applicationStorage.GetApplication(params.applicationId)
|
||||||
|
|
@ -80,19 +85,41 @@ export function createMainHandlerAdapter(mainHandler: Main): MainHandlerInterfac
|
||||||
throw new Error(`Application not found: ${params.applicationId}`)
|
throw new Error(`Application not found: ${params.applicationId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pay invoice from the app's balance
|
if (params.userPubkey) {
|
||||||
|
// Resolve the Nostr user's ApplicationUser to get their identifier
|
||||||
|
const appUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, params.userPubkey)
|
||||||
|
console.log(`[MainHandlerAdapter] Paying via PayAppUserInvoice from Nostr user ${params.userPubkey.slice(0, 8)}... (identifier: ${appUser.identifier})`)
|
||||||
|
|
||||||
|
// Use applicationManager.PayAppUserInvoice so notifyAppUserPayment fires
|
||||||
|
// This sends LiveUserOperation events via Nostr for real-time balance updates
|
||||||
|
const result = await mainHandler.applicationManager.PayAppUserInvoice(
|
||||||
|
params.applicationId,
|
||||||
|
{
|
||||||
|
invoice: params.paymentRequest,
|
||||||
|
amount: 0, // Use invoice amount
|
||||||
|
user_identifier: appUser.identifier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentHash: result.preimage || '',
|
||||||
|
feeSats: result.network_fee || 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: pay from app owner's balance (no Nostr user context)
|
||||||
const result = await mainHandler.paymentManager.PayInvoice(
|
const result = await mainHandler.paymentManager.PayInvoice(
|
||||||
app.owner.user_id,
|
app.owner.user_id,
|
||||||
{
|
{
|
||||||
invoice: params.paymentRequest,
|
invoice: params.paymentRequest,
|
||||||
amount: 0 // Use invoice amount
|
amount: 0
|
||||||
},
|
},
|
||||||
app, // linkedApplication
|
app,
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
paymentHash: result.preimage || '', // preimage serves as proof of payment
|
paymentHash: result.preimage || '',
|
||||||
feeSats: result.network_fee || 0
|
feeSats: result.network_fee || 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,9 @@ export interface ExtensionContext {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pay a Lightning invoice (requires sufficient balance)
|
* Pay a Lightning invoice (requires sufficient balance)
|
||||||
|
* If userPubkey is provided, pays from that user's balance instead of app.owner
|
||||||
*/
|
*/
|
||||||
payInvoice(applicationId: string, paymentRequest: string, maxFeeSats?: number): Promise<{
|
payInvoice(applicationId: string, paymentRequest: string, maxFeeSats?: number, userPubkey?: string): Promise<{
|
||||||
paymentHash: string
|
paymentHash: string
|
||||||
feeSats: number
|
feeSats: number
|
||||||
}>
|
}>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue