story-research-zapwall/hooks/useArticlePayment.ts
2026-01-15 02:45:27 +01:00

174 lines
6.4 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
}
interface UseArticlePaymentParams {
article: Article
pubkey: string | null
onUnlockSuccess?: (() => void) | undefined
connect?: (() => Promise<void>) | undefined
showToast?: ((message: string, variant?: 'success' | 'info' | 'warning' | 'error', duration?: number) => void) | undefined
}
export function useArticlePayment(params: UseArticlePaymentParams): 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: params.article, pubkey: params.pubkey, connect: params.connect, onUnlockSuccess: params.onUnlockSuccess, setLoading, setError, setPaymentInvoice, setPaymentHash, showToast: params.showToast })
const handlePaymentComplete = (): Promise<void> => checkPaymentAndUnlock({ article: params.article, pubkey: params.pubkey, paymentHash, onUnlockSuccess: params.onUnlockSuccess, setError, setPaymentInvoice, setPaymentHash, showToast: params.showToast })
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
showToast: ((message: string, variant?: 'success' | 'info' | 'warning' | 'error', duration?: number) => void) | undefined
}): Promise<void> {
const {pubkey} = params
if (!pubkey) {
await ensureConnectedOrError({
connect: params.connect,
setLoading: params.setLoading,
setError: params.setError,
})
return
}
await startArticlePaymentFlow({
article: params.article,
pubkey,
onUnlockSuccess: params.onUnlockSuccess,
setLoading: params.setLoading,
setError: params.setError,
setPaymentInvoice: params.setPaymentInvoice,
setPaymentHash: params.setPaymentHash,
showToast: params.showToast,
})
}
function readPaymentResult(value: Awaited<ReturnType<typeof paymentService.createArticlePayment>>): { invoice: AlbyInvoice; paymentHash: string } | null {
if (!value.success || !value.invoice || !value.paymentHash) {
return null
}
return { invoice: value.invoice, paymentHash: value.paymentHash }
}
async function startArticlePaymentFlow(params: {
article: Article
pubkey: string
onUnlockSuccess: (() => void) | undefined
setLoading: (value: boolean) => void
setError: (value: string | null) => void
setPaymentInvoice: (value: AlbyInvoice | null) => void
setPaymentHash: (value: string | null) => void
showToast: ((message: string, variant?: 'success' | 'info' | 'warning' | 'error', duration?: number) => void) | undefined
}): Promise<void> {
params.setLoading(true)
params.setError(null)
try {
const result = await paymentService.createArticlePayment({ article: params.article, userPubkey: params.pubkey })
const ok = readPaymentResult(result)
if (!ok) {
params.setError(result.error ?? 'Failed to create payment invoice')
return
}
params.setPaymentInvoice(ok.invoice)
params.setPaymentHash(ok.paymentHash)
void checkPaymentAndUnlock({ article: params.article, pubkey: params.pubkey, paymentHash: ok.paymentHash, onUnlockSuccess: params.onUnlockSuccess, setError: params.setError, setPaymentInvoice: params.setPaymentInvoice, setPaymentHash: params.setPaymentHash, showToast: params.showToast })
} 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
showToast: ((message: string, variant?: 'success' | 'info' | 'warning' | 'error', duration?: number) => void) | undefined
}): 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 })
if (params.showToast !== undefined) {
params.showToast('Article débloqué avec succès!', 'success')
}
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)
}