import { nostrService } from './nostr' import { waitForArticlePayment as waitForArticlePaymentHelper } from './paymentPolling' import { resolveArticleInvoice } from './invoiceResolver' import { PLATFORM_COMMISSIONS, calculateArticleSplit } from './platformCommissions' import type { Article } from '@/types/nostr' import type { AlbyInvoice } from '@/types/alby' export interface PaymentRequest { article: Article userPubkey: string } export interface PaymentResult { success: boolean invoice?: AlbyInvoice paymentHash?: string error?: string } /** * Payment service integrating Alby/WebLN Lightning payments with Nostr articles */ export class PaymentService { /** * Create a Lightning invoice for an article payment * First checks if author has created an invoice in the event tags, otherwise creates a new one */ private validateArticleAmount(zapAmount: number): { valid: boolean; error?: string } { const expectedAmount = PLATFORM_COMMISSIONS.article.total if (zapAmount !== expectedAmount) { return { valid: false, error: `Invalid article payment amount: ${zapAmount} sats. Expected ${expectedAmount} sats (700 to author, 100 commission)`, } } return { valid: true } } private validateInvoiceAmount(invoice: AlbyInvoice): { valid: boolean; error?: string } { const split = calculateArticleSplit() if (invoice.amount !== split.total) { return { valid: false, error: `Invoice amount mismatch: ${invoice.amount} sats. Expected ${split.total} sats (${split.author} to author, ${split.platform} commission)`, } } return { valid: true } } async createArticlePayment(request: PaymentRequest): Promise { try { const amountValidation = this.validateArticleAmount(request.article.zapAmount) if (!amountValidation.valid) { return { success: false, error: amountValidation.error ?? 'Invalid amount' } } const invoice = await resolveArticleInvoice(request.article) const invoiceValidation = this.validateInvoiceAmount(invoice) if (!invoiceValidation.valid) { return { success: false, error: invoiceValidation.error ?? 'Invalid invoice amount' } } await nostrService.createZapRequest(request.article.pubkey, request.article.id, request.article.zapAmount) return { success: true, invoice, paymentHash: invoice.paymentHash, } } catch (error) { console.error('Payment creation error:', error) return { success: false, error: error instanceof Error ? error.message : 'Failed to create payment', } } } /** * Check if payment for an article has been completed */ async checkArticlePayment( _paymentHash: string, articleId: string, articlePubkey: string, amount: number, userPubkey?: string ): Promise { try { // With Alby/WebLN, we rely on zap receipts for payment verification // since WebLN doesn't provide payment status checking const zapReceiptExists = await nostrService.checkZapReceipt( articlePubkey, articleId, amount, userPubkey ) return zapReceiptExists } catch (error) { console.error('Payment check error:', error) return false } } /** * Wait for payment completion with polling * After payment is confirmed, sends private content to the user */ waitForArticlePayment( paymentHash: string, articleId: string, articlePubkey: string, amount: number, recipientPubkey: string, timeout: number = 300000 // 5 minutes ): Promise { return waitForArticlePaymentHelper( paymentHash, articleId, articlePubkey, amount, recipientPubkey, timeout ) } /** * Get payment URL for display/QR code generation */ async getPaymentUrl(request: PaymentRequest): Promise { try { const result = await this.createArticlePayment(request) if (result.success && result.invoice) { // Return Lightning URI format return `lightning:${result.invoice.invoice}` } return null } catch (error) { console.error('Get payment URL error:', error) return null } } } export const paymentService = new PaymentService()