story-research-zapwall/lib/notifications.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

175 lines
4.6 KiB
TypeScript

import type { Event } from 'nostr-tools'
import { nostrService } from './nostr'
import { zapVerificationService } from './zapVerification'
import type { Notification } from '@/types/notifications'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
const RELAY_URL = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL ?? 'wss://relay.damus.io'
function createZapReceiptFilters(userPubkey: string) {
return [
{
kinds: [9735], // Zap receipt
'#p': [userPubkey], // Receipts targeting this user
},
]
}
async function buildPaymentNotification(event: Event, userPubkey: string): Promise<Notification | null> {
const paymentInfo = zapVerificationService.extractPaymentInfo(event)
if (paymentInfo?.recipient !== userPubkey) {
return null
}
let articleTitle: string | undefined
if (paymentInfo.articleId) {
try {
const article = await nostrService.getArticleById(paymentInfo.articleId)
articleTitle = article?.title
} catch (e) {
console.error('Error loading article for notification:', e)
}
}
return {
id: event.id,
type: 'payment',
title: 'New Payment Received',
message: articleTitle
? `Vous avez reçu un zap de ${paymentInfo.amount} sats pour "${articleTitle}"`
: `Vous avez reçu un zap de ${paymentInfo.amount} sats`,
timestamp: event.created_at,
read: false,
...(paymentInfo.articleId ? { articleId: paymentInfo.articleId } : {}),
...(articleTitle ? { articleTitle } : {}),
amount: paymentInfo.amount,
fromPubkey: paymentInfo.payer,
}
}
function registerZapSubscription(
sub: ReturnType<SimplePoolWithSub['sub']>,
userPubkey: string,
onNotification: (notification: Notification) => void
) {
sub.on('event', (event: Event) => {
void buildPaymentNotification(event, userPubkey)
.then((notification) => {
if (notification) {
onNotification(notification)
}
})
.catch((error) => {
console.error('Error processing zap receipt notification:', error)
})
})
}
/**
* Service for monitoring and managing notifications
*/
export class NotificationService {
private subscriptions: Map<string, () => void> = new Map()
/**
* Subscribe to zap receipts (payments) for a user's articles
*/
subscribeToPayments(
userPubkey: string,
onNotification: (notification: Notification) => void
): () => void {
const pool = nostrService.getPool()
if (!pool) {
return () => {}
}
const filters = createZapReceiptFilters(userPubkey)
const poolWithSub = pool as SimplePoolWithSub
const sub = poolWithSub.sub([RELAY_URL], filters)
registerZapSubscription(sub, userPubkey, onNotification)
const unsubscribe = () => {
sub.unsub()
}
return unsubscribe
}
/**
* Stop all subscriptions
*/
stopAll(): void {
this.subscriptions.forEach((unsubscribe) => unsubscribe())
this.subscriptions.clear()
}
}
export const notificationService = new NotificationService()
/**
* Load stored notifications from localStorage
*/
export function loadStoredNotifications(userPubkey: string): Notification[] {
try {
const key = `notifications_${userPubkey}`
const stored = localStorage.getItem(key)
if (stored) {
return JSON.parse(stored) as Notification[]
}
} catch (error) {
console.error('Error loading stored notifications:', error)
}
return []
}
/**
* Save notifications to localStorage
*/
export function saveNotifications(userPubkey: string, notifications: Notification[]): void {
try {
const key = `notifications_${userPubkey}`
localStorage.setItem(key, JSON.stringify(notifications))
} catch (error) {
console.error('Error saving notifications:', error)
}
}
/**
* Mark notification as read
*/
export function markNotificationAsRead(
userPubkey: string,
notificationId: string,
notifications: Notification[]
): Notification[] {
const updated = notifications.map((n) =>
n.id === notificationId ? { ...n, read: true } : n
)
saveNotifications(userPubkey, updated)
return updated
}
/**
* Mark all notifications as read
*/
export function markAllAsRead(userPubkey: string, notifications: Notification[]): Notification[] {
const updated = notifications.map((n) => ({ ...n, read: true }))
saveNotifications(userPubkey, updated)
return updated
}
/**
* Delete a notification
*/
export function deleteNotification(
userPubkey: string,
notificationId: string,
notifications: Notification[]
): Notification[] {
const updated = notifications.filter((n) => n.id !== notificationId)
saveNotifications(userPubkey, updated)
return updated
}