story-research-zapwall/hooks/useArticlePayment.ts
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

103 lines
2.7 KiB
TypeScript

import { useState } from 'react'
import type { Article } from '@/types/nostr'
import type { AlbyInvoice } from '@/types/alby'
import { paymentService } from '@/lib/payment'
import { nostrService } from '@/lib/nostr'
export function useArticlePayment(
article: Article,
pubkey: string | null,
onUnlockSuccess?: () => void,
connect?: () => Promise<void>
) {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [paymentInvoice, setPaymentInvoice] = useState<AlbyInvoice | null>(null)
const [paymentHash, setPaymentHash] = useState<string | null>(null)
const checkPaymentStatus = async (hash: string, userPubkey: string) => {
try {
const hasPaid = await paymentService.waitForArticlePayment(
hash,
article.id,
article.pubkey,
article.zapAmount,
userPubkey,
300000
)
if (hasPaid) {
const content = await nostrService.getPrivateContent(article.id, article.pubkey)
if (content) {
setPaymentInvoice(null)
setPaymentHash(null)
onUnlockSuccess?.()
} else {
setError('Content not available. Please contact the author.')
}
}
} catch (e) {
console.error('Payment check error:', e)
}
}
const handleUnlock = async () => {
if (!pubkey) {
if (connect) {
setLoading(true)
await connect()
setLoading(false)
} else {
setError('Please connect with Nostr first')
}
return
}
setLoading(true)
setError(null)
try {
const paymentResult = await paymentService.createArticlePayment({
article,
userPubkey: pubkey,
})
if (!paymentResult.success || !paymentResult.invoice || !paymentResult.paymentHash) {
setError(paymentResult.error ?? 'Failed to create payment invoice')
setLoading(false)
return
}
setPaymentInvoice(paymentResult.invoice)
setPaymentHash(paymentResult.paymentHash)
setLoading(false)
checkPaymentStatus(paymentResult.paymentHash, pubkey)
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Failed to process payment'
console.error('Payment processing error:', e)
setError(errorMessage)
setLoading(false)
}
}
const handlePaymentComplete = async () => {
if (paymentHash && pubkey) {
await checkPaymentStatus(paymentHash, pubkey)
}
}
const handleCloseModal = () => {
setPaymentInvoice(null)
setPaymentHash(null)
}
return {
loading,
error,
paymentInvoice,
handleUnlock,
handlePaymentComplete,
handleCloseModal,
}
}