- Création lib/platformCommissions.ts : configuration centralisée des commissions - Articles : 800 sats (700 auteur, 100 plateforme) - Avis : 70 sats (49 lecteur, 21 plateforme) - Sponsoring : 0.046 BTC (0.042 auteur, 0.004 plateforme) - Validation des montants à chaque étape : - Publication : vérification du montant avant publication - Paiement : vérification du montant avant acceptation - Erreurs explicites si montant incorrect - Tracking des commissions sur Nostr : - Tags author_amount et platform_commission dans événements - Interface ContentDeliveryTracking étendue - Traçabilité complète pour audit - Logs structurés avec informations de commission - Documentation complète du système Les commissions sont maintenant systématiques, validées et traçables.
141 lines
3.9 KiB
TypeScript
141 lines
3.9 KiB
TypeScript
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
|
|
*/
|
|
async createArticlePayment(request: PaymentRequest): Promise<PaymentResult> {
|
|
try {
|
|
// Verify article amount matches expected commission structure
|
|
const expectedAmount = PLATFORM_COMMISSIONS.article.total
|
|
if (request.article.zapAmount !== expectedAmount) {
|
|
return {
|
|
success: false,
|
|
error: `Invalid article payment amount: ${request.article.zapAmount} sats. Expected ${expectedAmount} sats (700 to author, 100 commission)`,
|
|
}
|
|
}
|
|
|
|
const invoice = await resolveArticleInvoice(request.article)
|
|
|
|
// Verify invoice amount matches expected commission structure
|
|
const split = calculateArticleSplit()
|
|
if (invoice.amount !== split.total) {
|
|
return {
|
|
success: false,
|
|
error: `Invoice amount mismatch: ${invoice.amount} sats. Expected ${split.total} sats (${split.author} to author, ${split.platform} commission)`,
|
|
}
|
|
}
|
|
|
|
// Create zap request event on Nostr
|
|
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<boolean> {
|
|
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<boolean> {
|
|
return waitForArticlePaymentHelper(
|
|
paymentHash,
|
|
articleId,
|
|
articlePubkey,
|
|
amount,
|
|
recipientPubkey,
|
|
timeout
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get payment URL for display/QR code generation
|
|
*/
|
|
async getPaymentUrl(request: PaymentRequest): Promise<string | null> {
|
|
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()
|