story-research-zapwall/hooks/useArticlePayment.ts
2026-01-10 09:41:57 +01:00

172 lines
4.8 KiB
TypeScript

import { useState } from 'react'
import type { Article } from '@/types/nostr'
import type { AlbyInvoice } from '@/types/alby'
import { paymentService } from '@/lib/payment'
import { nostrService } from '@/lib/nostr'
type UseArticlePaymentResult = {
loading: boolean
error: string | null
paymentInvoice: AlbyInvoice | null
handleUnlock: () => Promise<void>
handlePaymentComplete: () => Promise<void>
handleCloseModal: () => void
}
export function useArticlePayment(
article: Article,
pubkey: string | null,
onUnlockSuccess?: () => void,
connect?: () => Promise<void>
): UseArticlePaymentResult {
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [paymentInvoice, setPaymentInvoice] = useState<AlbyInvoice | null>(null)
const [paymentHash, setPaymentHash] = useState<string | null>(null)
const handleUnlock = (): Promise<void> =>
unlockArticlePayment({
article,
pubkey,
connect,
onUnlockSuccess,
setLoading,
setError,
setPaymentInvoice,
setPaymentHash,
})
const handlePaymentComplete = (): Promise<void> =>
checkPaymentAndUnlock({
article,
pubkey,
paymentHash,
onUnlockSuccess,
setError,
setPaymentInvoice,
setPaymentHash,
})
const handleCloseModal = (): void => resetPaymentModalState({ setPaymentInvoice, setPaymentHash })
return {
loading,
error,
paymentInvoice,
handleUnlock,
handlePaymentComplete,
handleCloseModal,
}
}
async function unlockArticlePayment(params: {
article: Article
pubkey: string | null
connect: (() => Promise<void>) | undefined
onUnlockSuccess: (() => void) | undefined
setLoading: (value: boolean) => void
setError: (value: string | null) => void
setPaymentInvoice: (value: AlbyInvoice | null) => void
setPaymentHash: (value: string | null) => void
}): Promise<void> {
if (!params.pubkey) {
await ensureConnectedOrError({
connect: params.connect,
setLoading: params.setLoading,
setError: params.setError,
})
return
}
params.setLoading(true)
params.setError(null)
try {
const paymentResult = await paymentService.createArticlePayment({
article: params.article,
userPubkey: params.pubkey,
})
if (!paymentResult.success || !paymentResult.invoice || !paymentResult.paymentHash) {
params.setError(paymentResult.error ?? 'Failed to create payment invoice')
return
}
params.setPaymentInvoice(paymentResult.invoice)
params.setPaymentHash(paymentResult.paymentHash)
void checkPaymentAndUnlock({
article: params.article,
pubkey: params.pubkey,
paymentHash: paymentResult.paymentHash,
onUnlockSuccess: params.onUnlockSuccess,
setError: params.setError,
setPaymentInvoice: params.setPaymentInvoice,
setPaymentHash: params.setPaymentHash,
})
} catch (e) {
const errorMessage = e instanceof Error ? e.message : 'Failed to process payment'
console.error('Payment processing error:', e)
params.setError(errorMessage)
} finally {
params.setLoading(false)
}
}
async function ensureConnectedOrError(params: {
connect: (() => Promise<void>) | undefined
setLoading: (value: boolean) => void
setError: (value: string | null) => void
}): Promise<void> {
if (!params.connect) {
params.setError('Please connect with Nostr first')
return
}
params.setLoading(true)
try {
await params.connect()
} finally {
params.setLoading(false)
}
}
async function checkPaymentAndUnlock(params: {
article: Article
pubkey: string | null
paymentHash: string | null
onUnlockSuccess: (() => void) | undefined
setError: (value: string | null) => void
setPaymentInvoice: (value: AlbyInvoice | null) => void
setPaymentHash: (value: string | null) => void
}): Promise<void> {
if (!params.paymentHash || !params.pubkey) {
return
}
try {
const hasPaid = await paymentService.waitForArticlePayment({
paymentHash: params.paymentHash,
articleId: params.article.id,
articlePubkey: params.article.pubkey,
amount: params.article.zapAmount,
recipientPubkey: params.pubkey,
timeout: 300000,
})
if (!hasPaid) {
return
}
const content = await nostrService.getPrivateContent(params.article.id, params.article.pubkey)
if (!content) {
params.setError('Content not available. Please contact the author.')
return
}
resetPaymentModalState({ setPaymentInvoice: params.setPaymentInvoice, setPaymentHash: params.setPaymentHash })
params.onUnlockSuccess?.()
} catch (e) {
console.error('Payment check error:', e)
}
}
function resetPaymentModalState(params: {
setPaymentInvoice: (value: AlbyInvoice | null) => void
setPaymentHash: (value: string | null) => void
}): void {
params.setPaymentInvoice(null)
params.setPaymentHash(null)
}