Improved payment buttons

This commit is contained in:
austinkelsay 2024-08-17 16:29:37 -05:00
parent 374bef5a51
commit 054adf6869
2 changed files with 124 additions and 212 deletions

View File

@ -1,148 +1,106 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog'; // Import Dialog component
import { initializeBitcoinConnect } from './BitcoinConnect'; import { initializeBitcoinConnect } from './BitcoinConnect';
import { LightningAddress } from '@getalby/lightning-tools'; import { LightningAddress } from '@getalby/lightning-tools';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import axios from 'axios'; // Import axios for API calls import axios from 'axios'; // Import axios for API calls
const PayButton = dynamic( const Payment = dynamic(
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton), () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment),
{ {
ssr: false, ssr: false,
} }
); );
const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId }) => { const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId }) => {
const [invoice, setInvoice] = useState(null); const [invoice, setInvoice] = useState(null);
const [userId, setUserId] = useState(null); const [userId, setUserId] = useState(null);
const { showToast } = useToast(); const { showToast } = useToast();
const [pollingInterval, setPollingInterval] = useState(null); const { data: session } = useSession();
const { data: session } = useSession(); const [dialogVisible, setDialogVisible] = useState(false); // New state for dialog visibility
useEffect(() => { useEffect(() => {
if (session?.user) { if (session?.user) {
setUserId(session.user.id); setUserId(session.user.id);
} }
}, [session]); }, [session]);
useEffect(() => { useEffect(() => {
initializeBitcoinConnect(); initializeBitcoinConnect();
}, []); }, []);
useEffect(() => { useEffect(() => {
const fetchInvoice = async () => { const fetchInvoice = async () => {
try { try {
const ln = new LightningAddress(lnAddress); const ln = new LightningAddress(lnAddress);
await ln.fetch(); await ln.fetch();
const invoice = await ln.requestInvoice({ satoshi: amount }); const invoice = await ln.requestInvoice({ satoshi: amount });
setInvoice(invoice); setInvoice(invoice);
} catch (error) { } catch (error) {
console.error('Error fetching invoice:', error); console.error('Error fetching invoice:', error);
showToast('error', 'Invoice Error', 'Failed to fetch the invoice.'); showToast('error', 'Invoice Error', 'Failed to fetch the invoice.');
if (onError) onError(error); if (onError) onError(error);
} }
};
fetchInvoice();
}, [lnAddress, amount, onError, showToast]);
const handlePaymentSuccess = async (response) => {
try {
const purchaseData = {
userId: userId,
courseId: courseId,
amountPaid: parseInt(amount, 10)
};
const result = await axios.post('/api/purchase/course', purchaseData);
if (result.status === 200) {
showToast('success', 'Payment Successful', `Paid ${amount} sats and updated user purchases`);
if (onSuccess) onSuccess(response);
} else {
throw new Error('Failed to update user purchases');
}
} catch (error) {
console.error('Error updating user purchases:', error);
showToast('error', 'Purchase Update Failed', 'Payment was successful, but failed to update user purchases.');
if (onError) onError(error);
}
setDialogVisible(false); // Close the dialog on successful payment
}; };
fetchInvoice(); return (
}, [lnAddress, amount, onError, showToast]); <>
<Button
const startPolling = (invoice) => { label={`Pay ${amount} sats`}
const intervalId = setInterval(async () => { onClick={() => setDialogVisible(true)}
try { disabled={!invoice}
const paid = await invoice.verifyPayment(); severity='info'
console.log('Polling for payment - Paid:', paid); className='text-[#f8f8ff] text-sm'
if (paid) { />
clearInterval(intervalId); // Stop polling <Dialog
handlePaymentSuccess(invoice); visible={dialogVisible}
} onHide={() => setDialogVisible(false)}
} catch (error) { header="Make Payment"
console.error('Polling error:', error); style={{ width: '50vw' }}
clearInterval(intervalId); // Stop polling on error >
handlePaymentError(error); {invoice ? (
} <Payment
}, 5000); // Poll every 5 seconds invoice={invoice.paymentRequest}
onPaid={handlePaymentSuccess}
setPollingInterval(intervalId); paymentMethods='all'
}; title={`Pay ${amount} sats`}
/>
const stopPolling = () => { ) : (
if (pollingInterval) { <p>Loading payment details...</p>
clearInterval(pollingInterval); )}
setPollingInterval(null); </Dialog>
} </>
}; );
const handlePaymentSuccess = async (response) => {
stopPolling();
await closeModal();
try {
const purchaseData = {
userId: userId,
courseId: courseId,
amountPaid: parseInt(amount, 10)
};
const result = await axios.post('/api/purchase/course', purchaseData);
if (result.status === 200) {
showToast('success', 'Payment Successful', `Paid ${amount} sats and updated user purchases`);
if (onSuccess) onSuccess(response);
} else {
throw new Error('Failed to update user purchases');
}
} catch (error) {
console.error('Error updating user purchases:', error);
showToast('error', 'Purchase Update Failed', 'Payment was successful, but failed to update user purchases.');
if (onError) onError(error);
}
};
const handlePaymentError = (error) => {
console.error('Payment failed:', error);
showToast('error', 'Payment Failed', error.message || 'An error occurred during payment.');
if (onError) onError(error);
stopPolling(); // Stop polling on error
};
const handleModalOpen = () => {
console.log('Modal opened');
if (invoice) {
startPolling(invoice); // Start polling when modal is opened
}
};
const handleModalClose = () => {
console.log('Modal closed');
stopPolling(); // Stop polling when modal is closed
};
const closeModal = async () => {
const { closeModal } = await import('@getalby/bitcoin-connect-react');
closeModal();
};
return (
<div className="flex items-center">
{invoice ? (
<PayButton
invoice={invoice.paymentRequest}
onClick={handleModalOpen}
onPaid={handlePaymentSuccess}
onModalClose={handleModalClose}
title={`Pay ${amount} sats`}
>
Pay Now
</PayButton>
) : (
<button disabled className="p-2 bg-gray-500 text-white rounded">
Loading...
</button>
)}
<span className="ml-2 text-white text-lg">{amount} sats</span>
</div>
);
}; };
export default CoursePaymentButton; export default CoursePaymentButton;

View File

@ -1,24 +1,24 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
import { initializeBitcoinConnect } from './BitcoinConnect'; import { initializeBitcoinConnect } from './BitcoinConnect';
import { LightningAddress } from '@getalby/lightning-tools'; import { LightningAddress } from '@getalby/lightning-tools';
import { useToast } from '@/hooks/useToast'; import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react'; import { useSession } from 'next-auth/react';
import axios from 'axios'; // Import axios for API calls import axios from 'axios';
const PayButton = dynamic( const Payment = dynamic(
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.PayButton), () => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment),
{ { ssr: false }
ssr: false,
}
); );
const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resourceId }) => { const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resourceId }) => {
const [invoice, setInvoice] = useState(null); const [invoice, setInvoice] = useState(null);
const [userId, setUserId] = useState(null); const [userId, setUserId] = useState(null);
const { showToast } = useToast(); const { showToast } = useToast();
const [pollingInterval, setPollingInterval] = useState(null);
const { data: session } = useSession(); const { data: session } = useSession();
const [dialogVisible, setDialogVisible] = useState(false);
useEffect(() => { useEffect(() => {
if (session?.user) { if (session?.user) {
@ -47,45 +47,14 @@ const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resource
fetchInvoice(); fetchInvoice();
}, [lnAddress, amount, onError, showToast]); }, [lnAddress, amount, onError, showToast]);
const startPolling = (invoice) => {
const intervalId = setInterval(async () => {
try {
const paid = await invoice.verifyPayment();
console.log('Polling for payment - Paid:', paid);
if (paid) {
clearInterval(intervalId); // Stop polling
handlePaymentSuccess(invoice);
}
} catch (error) {
console.error('Polling error:', error);
clearInterval(intervalId); // Stop polling on error
handlePaymentError(error);
}
}, 5000); // Poll every 5 seconds
setPollingInterval(intervalId);
};
const stopPolling = () => {
if (pollingInterval) {
clearInterval(pollingInterval);
setPollingInterval(null);
}
};
const handlePaymentSuccess = async (response) => { const handlePaymentSuccess = async (response) => {
stopPolling();
await closeModal();
try { try {
// Create a new purchase record
const purchaseData = { const purchaseData = {
userId: userId, userId: userId,
resourceId: resourceId, resourceId: resourceId,
amountPaid: parseInt(amount, 10) // Convert amount to integer amountPaid: parseInt(amount, 10)
}; };
// Make an API call to add the purchase to the user
const result = await axios.post('/api/purchase/resource', purchaseData); const result = await axios.post('/api/purchase/resource', purchaseData);
if (result.status === 200) { if (result.status === 200) {
@ -99,51 +68,36 @@ const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resource
showToast('error', 'Purchase Update Failed', 'Payment was successful, but failed to update user purchases.'); showToast('error', 'Purchase Update Failed', 'Payment was successful, but failed to update user purchases.');
if (onError) onError(error); if (onError) onError(error);
} }
}; setDialogVisible(false);
const handlePaymentError = (error) => {
console.error('Payment failed:', error);
showToast('error', 'Payment Failed', error.message || 'An error occurred during payment.');
if (onError) onError(error);
stopPolling(); // Stop polling on error
};
const handleModalOpen = () => {
console.log('Modal opened');
if (invoice) {
startPolling(invoice); // Start polling when modal is opened
}
};
const handleModalClose = () => {
console.log('Modal closed');
stopPolling(); // Stop polling when modal is closed
};
const closeModal = async () => {
const { closeModal } = await import('@getalby/bitcoin-connect-react');
closeModal();
}; };
return ( return (
<div className="flex items-center"> <>
{invoice ? ( <Button
<PayButton label={`Pay ${amount} sats`}
invoice={invoice.paymentRequest} icon="pi pi-wallet"
onClick={handleModalOpen} onClick={() => setDialogVisible(true)}
onPaid={handlePaymentSuccess} disabled={!invoice}
onModalClose={handleModalClose} className="p-2 bg-blue-500 text-white rounded"
title={`Pay ${amount} sats`} />
> <Dialog
Pay Now visible={dialogVisible}
</PayButton> onHide={() => setDialogVisible(false)}
) : ( header="Make Payment"
<button disabled className="p-2 bg-gray-500 text-white rounded"> style={{ width: '50vw' }}
Loading... >
</button> {invoice ? (
)} <Payment
<span className="ml-2 text-white text-lg">{amount} sats</span> invoice={invoice.paymentRequest}
</div> onPaid={handlePaymentSuccess}
paymentMethods='all'
title={`Pay ${amount} sats`}
/>
) : (
<p>Loading payment details...</p>
)}
</Dialog>
</>
); );
}; };