story-research-zapwall/lib/sponsoringPayment.ts
Nicolas Cantu 7364d6a83e feat: Implémentation split sponsoring, avis et transfert automatique
- Split pour sponsoring (Bitcoin mainnet) :
  - Service SponsoringPaymentService avec calcul split (0.042/0.004 BTC)
  - Validation montants et adresses Bitcoin
  - Structure pour vérification transactions

- Split pour avis (Lightning) :
  - Service ReviewRewardService avec commission (49/21 sats)
  - Création invoice avec split
  - Transfert automatique reviewer portion
  - Mise à jour avis avec tag rewarded

- Système transfert automatique :
  - Service AutomaticTransferService
  - Transfert auteur portion après paiement article
  - Transfert reviewer portion après rémunération avis
  - Tracking et logs structurés

- Intégration dans paymentPolling pour articles
- Documentation complète du système

Les services sont prêts pour intégration avec nœud Lightning et services blockchain.
2025-12-27 21:13:16 +01:00

193 lines
6.0 KiB
TypeScript

import { calculateSponsoringSplit, PLATFORM_COMMISSIONS, PLATFORM_BITCOIN_ADDRESS } from './platformCommissions'
import { platformTracking } from './platformTracking'
/**
* Sponsoring payment service
* Handles Bitcoin mainnet payments for sponsoring with automatic commission split
*
* Sponsoring: 0.046 BTC total
* - Author: 0.042 BTC (4,200,000 sats)
* - Platform: 0.004 BTC (400,000 sats)
*
* Since Bitcoin mainnet doesn't support automatic split like Lightning,
* we use a two-output transaction approach:
* 1. User creates transaction with two outputs (author + platform)
* 2. Platform verifies both outputs are present
* 3. Transaction is broadcast
*/
export interface SponsoringPaymentRequest {
authorPubkey: string
authorMainnetAddress: string
amount: number // Should be 0.046 BTC
}
export interface SponsoringPaymentResult {
success: boolean
transactionId?: string
error?: string
split: {
author: number
platform: number
total: number
authorSats: number
platformSats: number
totalSats: number
}
platformAddress: string
authorAddress: string
}
export class SponsoringPaymentService {
/**
* Create sponsoring payment request with split information
* Returns addresses and amounts for the user to create a Bitcoin transaction
*/
async createSponsoringPayment(request: SponsoringPaymentRequest): Promise<SponsoringPaymentResult> {
try {
// Verify amount matches expected commission structure
if (request.amount !== PLATFORM_COMMISSIONS.sponsoring.total) {
return {
success: false,
error: `Invalid sponsoring amount: ${request.amount} BTC. Expected ${PLATFORM_COMMISSIONS.sponsoring.total} BTC`,
split: calculateSponsoringSplit(),
platformAddress: PLATFORM_BITCOIN_ADDRESS,
authorAddress: request.authorMainnetAddress,
}
}
const split = calculateSponsoringSplit(request.amount)
// Verify addresses are valid Bitcoin addresses
if (!this.isValidBitcoinAddress(request.authorMainnetAddress)) {
return {
success: false,
error: 'Invalid author Bitcoin address',
split,
platformAddress: PLATFORM_BITCOIN_ADDRESS,
authorAddress: request.authorMainnetAddress,
}
}
console.log('Sponsoring payment request created', {
authorPubkey: request.authorPubkey,
authorAddress: request.authorMainnetAddress,
platformAddress: PLATFORM_BITCOIN_ADDRESS,
authorAmount: split.authorSats,
platformAmount: split.platformSats,
totalAmount: split.totalSats,
timestamp: new Date().toISOString(),
})
return {
success: true,
split,
platformAddress: PLATFORM_BITCOIN_ADDRESS,
authorAddress: request.authorMainnetAddress,
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
console.error('Error creating sponsoring payment', {
authorPubkey: request.authorPubkey,
error: errorMessage,
timestamp: new Date().toISOString(),
})
return {
success: false,
error: errorMessage,
split: calculateSponsoringSplit(),
platformAddress: PLATFORM_BITCOIN_ADDRESS,
authorAddress: request.authorMainnetAddress,
}
}
}
/**
* Verify sponsoring payment transaction
* Checks that transaction has correct outputs for both author and platform
*/
async verifySponsoringPayment(
transactionId: string,
authorPubkey: string,
authorMainnetAddress: string
): Promise<boolean> {
try {
const split = calculateSponsoringSplit()
// In production, this would:
// 1. Fetch transaction from blockchain
// 2. Verify it has two outputs:
// - Output 1: split.authorSats to authorMainnetAddress
// - Output 2: split.platformSats to PLATFORM_BITCOIN_ADDRESS
// 3. Verify transaction is confirmed
console.log('Verifying sponsoring payment', {
transactionId,
authorPubkey,
authorAddress: authorMainnetAddress,
platformAddress: PLATFORM_BITCOIN_ADDRESS,
expectedAuthorAmount: split.authorSats,
expectedPlatformAmount: split.platformSats,
timestamp: new Date().toISOString(),
})
// For now, return true (in production, implement actual verification)
return true
} catch (error) {
console.error('Error verifying sponsoring payment', {
transactionId,
authorPubkey,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
return false
}
}
/**
* Track sponsoring payment
*/
async trackSponsoringPayment(
transactionId: string,
authorPubkey: string,
authorMainnetAddress: string,
authorPrivateKey: string
): Promise<void> {
try {
const split = calculateSponsoringSplit()
// Track the sponsoring payment on Nostr
// This would be similar to article payment tracking
console.log('Tracking sponsoring payment', {
transactionId,
authorPubkey,
authorAddress: authorMainnetAddress,
platformAddress: PLATFORM_BITCOIN_ADDRESS,
authorAmount: split.authorSats,
platformCommission: split.platformSats,
timestamp: new Date().toISOString(),
})
// In production, publish tracking event on Nostr
} catch (error) {
console.error('Error tracking sponsoring payment', {
transactionId,
authorPubkey,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
}
}
/**
* Validate Bitcoin address format
*/
private isValidBitcoinAddress(address: string): boolean {
// Basic validation: starts with 1, 3, or bc1
const bitcoinAddressRegex = /^(1|3|bc1)[a-zA-Z0-9]{25,62}$/
return bitcoinAddressRegex.test(address)
}
}
export const sponsoringPaymentService = new SponsoringPaymentService()