- 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.
193 lines
6.0 KiB
TypeScript
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()
|
|
|