diff --git a/src/extensions/context.ts b/src/extensions/context.ts index b1c6e8d6..f18891f5 100644 --- a/src/extensions/context.ts +++ b/src/extensions/context.ts @@ -20,6 +20,17 @@ export interface MainHandlerInterface { // Application management applicationManager: { getById(id: string): Promise + 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 @@ -41,6 +52,7 @@ export interface MainHandlerInterface { applicationId: string paymentRequest: string maxFeeSats?: number + userPubkey?: string }): Promise<{ paymentHash: string feeSats: number @@ -156,16 +168,19 @@ export class ExtensionContextImpl implements ExtensionContext { /** * Pay a Lightning invoice + * If userPubkey is provided, pays from that user's balance instead of app.owner */ async payInvoice( applicationId: string, paymentRequest: string, - maxFeeSats?: number + maxFeeSats?: number, + userPubkey?: string ): Promise<{ paymentHash: string; feeSats: number }> { return this.mainHandler.paymentManager.payInvoice({ applicationId, paymentRequest, - maxFeeSats + maxFeeSats, + userPubkey }) } diff --git a/src/extensions/mainHandlerAdapter.ts b/src/extensions/mainHandlerAdapter.ts index eecc96b3..fec73c3f 100644 --- a/src/extensions/mainHandlerAdapter.ts +++ b/src/extensions/mainHandlerAdapter.ts @@ -32,6 +32,10 @@ export function createMainHandlerAdapter(mainHandler: Main): MainHandlerInterfac // GetApplication throws if not found return null } + }, + + async PayAppUserInvoice(appId, req) { + return mainHandler.applicationManager.PayAppUserInvoice(appId, req) } }, @@ -73,6 +77,7 @@ export function createMainHandlerAdapter(mainHandler: Main): MainHandlerInterfac applicationId: string paymentRequest: string maxFeeSats?: number + userPubkey?: string }) { // Get the app to find the user ID and app reference 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}`) } - // 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( app.owner.user_id, { invoice: params.paymentRequest, - amount: 0 // Use invoice amount + amount: 0 }, - app, // linkedApplication + app, {} ) return { - paymentHash: result.preimage || '', // preimage serves as proof of payment + paymentHash: result.preimage || '', feeSats: result.network_fee || 0 } }, diff --git a/src/extensions/types.ts b/src/extensions/types.ts index 2027fb09..66a4c46a 100644 --- a/src/extensions/types.ts +++ b/src/extensions/types.ts @@ -140,8 +140,9 @@ export interface ExtensionContext { /** * 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 feeSats: number }>