202 lines
6.2 KiB
TypeScript
202 lines
6.2 KiB
TypeScript
import { calculateArticleSplit, calculateReviewSplit } from './platformCommissions'
|
|
|
|
/**
|
|
* Automatic transfer service
|
|
* Handles automatic forwarding of author/reviewer portions after payment
|
|
*
|
|
* Since WebLN doesn't support BOLT12 with split, the platform receives the full amount
|
|
* and must automatically forward the author/reviewer portion.
|
|
*
|
|
* This service tracks transfers and ensures they are executed correctly.
|
|
*/
|
|
export interface TransferResult {
|
|
success: boolean
|
|
transferInvoiceId?: string
|
|
error?: string
|
|
amount: number
|
|
recipient: string
|
|
}
|
|
|
|
export class AutomaticTransferService {
|
|
/**
|
|
* Transfer author portion after article payment
|
|
* Creates a Lightning invoice from the platform to the author
|
|
*/
|
|
private logTransferRequired(params: {
|
|
type: 'article' | 'review'
|
|
id: string
|
|
pubkey: string
|
|
amount: number
|
|
recipient: string
|
|
platformCommission: number
|
|
}): void {
|
|
const logData = {
|
|
[params.type === 'article' ? 'articleId' : 'reviewId']: params.id,
|
|
[params.type === 'article' ? 'articlePubkey' : 'reviewerPubkey']: params.pubkey,
|
|
amount: params.amount,
|
|
recipient: params.recipient,
|
|
platformCommission: params.platformCommission,
|
|
timestamp: new Date().toISOString(),
|
|
}
|
|
console.warn(`Automatic transfer required${params.type === 'review' ? ' for review' : ''}`, logData)
|
|
}
|
|
|
|
private buildTransferError(error: unknown, recipient: string, amount: number = 0): TransferResult {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
return {
|
|
success: false,
|
|
error: errorMessage,
|
|
amount,
|
|
recipient,
|
|
}
|
|
}
|
|
|
|
async transferAuthorPortion(
|
|
authorLightningAddress: string,
|
|
articleId: string,
|
|
articlePubkey: string,
|
|
paymentAmount: number
|
|
): Promise<TransferResult> {
|
|
return this.transferPortion({
|
|
type: 'article',
|
|
id: articleId,
|
|
pubkey: articlePubkey,
|
|
recipient: authorLightningAddress,
|
|
paymentAmount,
|
|
computeSplit: calculateArticleSplit,
|
|
getRecipientAmount: (split) => split.author,
|
|
missingRecipientError: 'Author Lightning address not available',
|
|
errorLogMessage: 'Error transferring author portion',
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Transfer reviewer portion after review reward payment
|
|
*/
|
|
async transferReviewerPortion(
|
|
reviewerLightningAddress: string,
|
|
reviewId: string,
|
|
reviewerPubkey: string,
|
|
paymentAmount: number
|
|
): Promise<TransferResult> {
|
|
return this.transferPortion({
|
|
type: 'review',
|
|
id: reviewId,
|
|
pubkey: reviewerPubkey,
|
|
recipient: reviewerLightningAddress,
|
|
paymentAmount,
|
|
computeSplit: calculateReviewSplit,
|
|
getRecipientAmount: (split) => split.reviewer,
|
|
missingRecipientError: 'Reviewer Lightning address not available',
|
|
errorLogMessage: 'Error transferring reviewer portion',
|
|
})
|
|
}
|
|
|
|
private async transferPortion(params: {
|
|
type: 'article' | 'review'
|
|
id: string
|
|
pubkey: string
|
|
recipient: string
|
|
paymentAmount: number
|
|
computeSplit: (amount: number) => { platform: number } & Record<string, number>
|
|
getRecipientAmount: (split: { platform: number } & Record<string, number>) => number
|
|
missingRecipientError: string
|
|
errorLogMessage: string
|
|
}): Promise<TransferResult> {
|
|
try {
|
|
const split = params.computeSplit(params.paymentAmount)
|
|
const recipientAmount = params.getRecipientAmount(split)
|
|
|
|
if (!params.recipient) {
|
|
return this.buildMissingRecipientResult({
|
|
error: params.missingRecipientError,
|
|
recipient: params.recipient,
|
|
amount: recipientAmount,
|
|
})
|
|
}
|
|
|
|
this.logAndTrackTransferRequirement({
|
|
type: params.type,
|
|
id: params.id,
|
|
pubkey: params.pubkey,
|
|
recipient: params.recipient,
|
|
amount: recipientAmount,
|
|
platformCommission: split.platform,
|
|
})
|
|
|
|
return { success: true, amount: recipientAmount, recipient: params.recipient }
|
|
} catch (error) {
|
|
this.logTransferError({ message: params.errorLogMessage, id: params.id, pubkey: params.pubkey, error })
|
|
return this.buildTransferError(error, params.recipient)
|
|
}
|
|
}
|
|
|
|
private buildMissingRecipientResult(params: { error: string; recipient: string; amount: number }): TransferResult {
|
|
return { success: false, error: params.error, amount: params.amount, recipient: params.recipient }
|
|
}
|
|
|
|
private logAndTrackTransferRequirement(params: {
|
|
type: 'article' | 'review'
|
|
id: string
|
|
pubkey: string
|
|
recipient: string
|
|
amount: number
|
|
platformCommission: number
|
|
}): void {
|
|
this.logTransferRequired({
|
|
type: params.type,
|
|
id: params.id,
|
|
pubkey: params.pubkey,
|
|
amount: params.amount,
|
|
recipient: params.recipient,
|
|
platformCommission: params.platformCommission,
|
|
})
|
|
this.trackTransferRequirement({
|
|
type: params.type,
|
|
id: params.id,
|
|
recipientPubkey: params.pubkey,
|
|
amount: params.amount,
|
|
recipientAddress: params.recipient,
|
|
})
|
|
}
|
|
|
|
private logTransferError(params: { message: string; id: string; pubkey: string; error: unknown }): void {
|
|
console.error(params.message, {
|
|
id: params.id,
|
|
pubkey: params.pubkey,
|
|
error: params.error instanceof Error ? params.error.message : 'Unknown error',
|
|
timestamp: new Date().toISOString(),
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Track transfer requirement for later processing
|
|
* In production, this would be stored in a database or queue
|
|
*/
|
|
private trackTransferRequirement(
|
|
params: {
|
|
type: 'article' | 'review'
|
|
id: string
|
|
recipientPubkey: string
|
|
amount: number
|
|
recipientAddress: string
|
|
}
|
|
): void {
|
|
// In production, this would:
|
|
// 1. Store in a database/queue for processing
|
|
// 2. Trigger automatic transfer via platform's Lightning node
|
|
// 3. Update tracking when transfer is complete
|
|
|
|
console.warn('Transfer requirement tracked', {
|
|
type: params.type,
|
|
id: params.id,
|
|
recipientPubkey: params.recipientPubkey,
|
|
amount: params.amount,
|
|
recipientAddress: params.recipientAddress,
|
|
timestamp: new Date().toISOString(),
|
|
})
|
|
}
|
|
}
|
|
|
|
export const automaticTransferService = new AutomaticTransferService()
|