Nicolas Cantu 90ff8282f1 feat: Implémentation système de commissions systématique et incontournable
- 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.
2025-12-27 21:11:09 +01:00

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()