story-research-zapwall/lib/articleInvoice.ts
2026-01-09 13:13:24 +01:00

157 lines
4.9 KiB
TypeScript

import { getAlbyService } from './alby'
import { calculateArticleSplit, PLATFORM_COMMISSIONS } from './platformCommissions'
import { buildTags } from './nostrTagSystem'
import { PLATFORM_SERVICE } from './platformConfig'
import { generatePublicationHashId } from './hashIdGenerator'
import type { AlbyInvoice } from '@/types/alby'
import type { ArticleDraft } from './articlePublisher'
/**
* Create Lightning invoice for article with automatic commission split
* The invoice is created for the full amount (800 sats) which includes:
* - 700 sats for the author
* - 100 sats commission for the platform
*
* The commission is automatically tracked and the split is enforced.
* Requires Alby/WebLN to be available and enabled
*/
export async function createArticleInvoice(draft: ArticleDraft): Promise<AlbyInvoice> {
// Verify amount matches expected commission structure
if (draft.zapAmount !== PLATFORM_COMMISSIONS.article.total) {
throw new Error(
`Invalid article payment amount: ${draft.zapAmount} sats. Expected ${PLATFORM_COMMISSIONS.article.total} sats (700 to author, 100 commission)`
)
}
const split = calculateArticleSplit()
// Get author's Lightning address from their profile or use platform address as fallback
// For now, we'll create the invoice through the platform's wallet
// The platform will forward the author's portion after payment
const alby = getAlbyService()
await alby.enable()
const invoice = await alby.createInvoice({
amount: split.total,
description: `Article: ${draft.title} (${split.author} sats author, ${split.platform} sats commission)`,
expiry: 86400, // 24 hours
})
return invoice
}
/**
* Create preview event with invoice tags
* If encryptedContent is provided, it will be used instead of preview
*/
export async function createPreviewEvent(
params: {
draft: ArticleDraft
invoice: AlbyInvoice
authorPubkey: string
authorPresentationId?: string
extraTags?: string[][]
encryptedContent?: string
encryptedKey?: string
}
): Promise<{
kind: 1
created_at: number
tags: string[][]
content: string
}> {
const tags = await buildPreviewTags({
draft: params.draft,
invoice: params.invoice,
authorPubkey: params.authorPubkey,
...(params.authorPresentationId ? { authorPresentationId: params.authorPresentationId } : {}),
...(params.extraTags ? { extraTags: params.extraTags } : {}),
...(params.encryptedKey ? { encryptedKey: params.encryptedKey } : {}),
})
return {
kind: 1 as const,
created_at: Math.floor(Date.now() / 1000),
tags,
content: params.encryptedContent ?? params.draft.preview,
}
}
async function buildPreviewTags(
params: {
draft: ArticleDraft
invoice: AlbyInvoice
authorPubkey: string
authorPresentationId?: string
extraTags?: string[][]
encryptedKey?: string
}
): Promise<string[][]> {
const category = normalizePublicationCategory(params.draft.category)
// Generate hash ID from publication data
const hashId = await generatePublicationHashId({
pubkey: params.authorPubkey,
title: params.draft.title,
preview: params.draft.preview,
category,
seriesId: params.draft.seriesId ?? undefined,
bannerUrl: params.draft.bannerUrl ?? undefined,
zapAmount: params.draft.zapAmount,
})
// Build tags using new system
const newTags = buildTags({
type: 'publication',
category,
id: hashId,
service: PLATFORM_SERVICE,
version: 0, // New object
hidden: false,
paywall: true, // Publications are paid
title: params.draft.title,
preview: params.draft.preview,
zapAmount: params.draft.zapAmount,
invoice: params.invoice.invoice,
paymentHash: params.invoice.paymentHash,
...(params.draft.seriesId ? { seriesId: params.draft.seriesId } : {}),
...(params.draft.bannerUrl ? { bannerUrl: params.draft.bannerUrl } : {}),
...(params.encryptedKey ? { encryptedKey: params.encryptedKey } : {}),
})
// Build JSON metadata
const publicationJson = JSON.stringify({
type: 'publication',
pubkey: params.authorPubkey,
title: params.draft.title,
preview: params.draft.preview,
category,
seriesId: params.draft.seriesId,
bannerUrl: params.draft.bannerUrl,
zapAmount: params.draft.zapAmount,
invoice: params.invoice.invoice,
paymentHash: params.invoice.paymentHash,
id: hashId,
version: 0,
index: 0,
...(params.draft.pages && params.draft.pages.length > 0 ? { pages: params.draft.pages } : {}),
})
// Add JSON metadata as a tag
newTags.push(['json', publicationJson])
// Add any extra tags (for backward compatibility)
if (params.extraTags && params.extraTags.length > 0) {
newTags.push(...params.extraTags)
}
return newTags
}
function normalizePublicationCategory(category: ArticleDraft['category'] | undefined): 'sciencefiction' | 'research' {
if (category === 'scientific-research') {
return 'research'
}
return 'sciencefiction'
}