174 lines
6.4 KiB
TypeScript
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)
|
|
}
|