2024-08-30 19:37:13 -05:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
import { Button } from 'primereact/button';
|
2024-08-31 18:18:21 -05:00
|
|
|
import { ProgressSpinner } from 'primereact/progressspinner';
|
2024-08-30 19:37:13 -05:00
|
|
|
import { initializeBitcoinConnect } from './BitcoinConnect';
|
|
|
|
import { LightningAddress } from '@getalby/lightning-tools';
|
|
|
|
import { useToast } from '@/hooks/useToast';
|
|
|
|
import { useSession } from 'next-auth/react';
|
2024-08-31 18:18:21 -05:00
|
|
|
import { webln, nwc } from '@getalby/sdk';
|
|
|
|
import { useRouter } from 'next/router';
|
2024-08-31 21:52:50 -05:00
|
|
|
import { Divider } from 'primereact/divider';
|
2024-08-30 19:37:13 -05:00
|
|
|
import dynamic from 'next/dynamic';
|
2024-08-31 18:18:21 -05:00
|
|
|
import AlbyButton from '@/components/buttons/AlbyButton';
|
2024-09-10 15:44:08 -05:00
|
|
|
import GenericButton from '@/components/buttons/GenericButton';
|
2024-08-31 18:18:21 -05:00
|
|
|
import axios from 'axios';
|
2024-08-31 21:52:50 -05:00
|
|
|
import Image from 'next/image';
|
|
|
|
|
2024-08-30 19:37:13 -05:00
|
|
|
const PaymentModal = dynamic(
|
|
|
|
() => import('@getalby/bitcoin-connect-react').then((mod) => mod.Payment),
|
|
|
|
{ ssr: false }
|
|
|
|
);
|
|
|
|
|
2024-09-25 20:57:14 -05:00
|
|
|
const SubscriptionPaymentButtons = ({ onSuccess, onError, onRecurringSubscriptionSuccess, setIsProcessing, oneTime = false, recurring = false, layout = "row" }) => {
|
2024-08-30 19:37:13 -05:00
|
|
|
const [invoice, setInvoice] = useState(null);
|
2024-08-31 18:18:21 -05:00
|
|
|
const [showRecurringOptions, setShowRecurringOptions] = useState(false);
|
|
|
|
const [nwcInput, setNwcInput] = useState('');
|
2024-08-30 19:37:13 -05:00
|
|
|
const { showToast } = useToast();
|
2024-09-15 15:53:27 -05:00
|
|
|
const { data: session, status } = useSession();
|
|
|
|
const router = useRouter();
|
2024-08-30 19:37:13 -05:00
|
|
|
|
|
|
|
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
|
|
|
|
const amount = 25;
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
initializeBitcoinConnect();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
let intervalId;
|
|
|
|
if (invoice) {
|
|
|
|
intervalId = setInterval(async () => {
|
|
|
|
const paid = await invoice.verifyPayment();
|
|
|
|
|
|
|
|
if (paid && invoice.preimage) {
|
|
|
|
clearInterval(intervalId);
|
|
|
|
// handle success
|
|
|
|
onSuccess();
|
|
|
|
}
|
|
|
|
}, 1000);
|
|
|
|
} else {
|
|
|
|
console.log('no invoice');
|
|
|
|
}
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
if (intervalId) {
|
|
|
|
clearInterval(intervalId);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}, [invoice]);
|
|
|
|
|
|
|
|
const fetchInvoice = async () => {
|
|
|
|
try {
|
|
|
|
const ln = new LightningAddress(lnAddress);
|
|
|
|
await ln.fetch();
|
|
|
|
const newInvoice = await ln.requestInvoice({ satoshi: amount });
|
|
|
|
console.log('newInvoice', newInvoice);
|
|
|
|
return newInvoice;
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error fetching invoice:', error);
|
2024-08-31 18:18:21 -05:00
|
|
|
showToast('error', 'Invoice Error', `Failed to fetch the invoice: ${error.message}`);
|
2024-08-30 19:37:13 -05:00
|
|
|
if (onError) onError(error);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handlePaymentSuccess = async (response) => {
|
|
|
|
console.log('Payment successful', response);
|
2024-08-31 18:18:21 -05:00
|
|
|
showToast('success', 'Payment Successful', 'Your payment has been processed successfully.');
|
|
|
|
if (onSuccess) onSuccess(response);
|
2024-08-30 19:37:13 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
const handlePaymentError = async (error) => {
|
|
|
|
console.error('Payment error', error);
|
2024-08-31 18:18:21 -05:00
|
|
|
showToast('error', 'Payment Failed', `An error occurred during payment: ${error.message}`);
|
|
|
|
if (onError) onError(error);
|
2024-08-30 19:37:13 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
const handleRecurringSubscription = async () => {
|
2024-08-31 18:18:21 -05:00
|
|
|
setIsProcessing(true);
|
|
|
|
const newNwc = nwc.NWCClient.withNewSecret();
|
|
|
|
const yearFromNow = new Date();
|
|
|
|
yearFromNow.setFullYear(yearFromNow.getFullYear() + 1);
|
|
|
|
|
|
|
|
try {
|
|
|
|
const initNwcOptions = {
|
|
|
|
name: "plebdevs.com",
|
|
|
|
requestMethods: ['pay_invoice'],
|
|
|
|
maxAmount: 25,
|
|
|
|
editable: false,
|
2024-10-06 14:44:20 -05:00
|
|
|
budgetRenewal: 'daily',
|
2024-08-31 18:18:21 -05:00
|
|
|
expiresAt: yearFromNow,
|
|
|
|
};
|
|
|
|
await newNwc.initNWC(initNwcOptions);
|
|
|
|
showToast('info', 'Alby', 'Alby connection window opened.');
|
|
|
|
const newNWCUrl = newNwc.getNostrWalletConnectUrl();
|
|
|
|
|
|
|
|
if (newNWCUrl) {
|
2024-09-01 12:40:25 -05:00
|
|
|
const nwc = new webln.NostrWebLNProvider({
|
|
|
|
nostrWalletConnectUrl: newNWCUrl,
|
|
|
|
});
|
|
|
|
|
|
|
|
await nwc.enable();
|
|
|
|
|
|
|
|
const invoice = await fetchInvoice();
|
|
|
|
|
|
|
|
if (!invoice || !invoice.paymentRequest) {
|
|
|
|
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const paymentResponse = await nwc.sendPayment(invoice.paymentRequest);
|
|
|
|
|
|
|
|
if (!paymentResponse || !paymentResponse?.preimage) {
|
|
|
|
showToast('error', 'NWC', 'Payment failed');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-08-31 18:18:21 -05:00
|
|
|
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
|
|
|
userId: session.user.id,
|
|
|
|
isSubscribed: true,
|
|
|
|
nwc: newNWCUrl,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (subscriptionResponse.status === 200) {
|
|
|
|
showToast('success', 'Subscription Setup', 'Recurring subscription setup successful!');
|
|
|
|
if (onRecurringSubscriptionSuccess) onRecurringSubscriptionSuccess();
|
|
|
|
} else {
|
|
|
|
throw new Error(`Unexpected response status: ${subscriptionResponse.status}`);
|
2024-08-30 19:37:13 -05:00
|
|
|
}
|
2024-08-31 18:18:21 -05:00
|
|
|
} else {
|
|
|
|
throw new Error('Failed to generate NWC URL');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Error initializing NWC:', error);
|
|
|
|
showToast('error', 'Subscription Setup Failed', `Error: ${error.message}`);
|
|
|
|
if (onError) onError(error);
|
|
|
|
} finally {
|
|
|
|
setIsProcessing(false);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleManualNwcSubmit = async () => {
|
|
|
|
if (!nwcInput) {
|
|
|
|
showToast('error', 'NWC', 'Please enter a valid NWC URL');
|
|
|
|
return;
|
|
|
|
}
|
2024-08-30 19:37:13 -05:00
|
|
|
|
2024-08-31 18:18:21 -05:00
|
|
|
setIsProcessing(true);
|
|
|
|
try {
|
|
|
|
const nwc = new webln.NostrWebLNProvider({
|
|
|
|
nostrWalletConnectUrl: nwcInput,
|
|
|
|
});
|
2024-08-30 19:37:13 -05:00
|
|
|
|
2024-08-31 18:18:21 -05:00
|
|
|
await nwc.enable();
|
2024-08-30 19:37:13 -05:00
|
|
|
|
2024-08-31 18:18:21 -05:00
|
|
|
const invoice = await fetchInvoice();
|
|
|
|
if (!invoice || !invoice.paymentRequest) {
|
|
|
|
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const payResponse = await nwc.sendPayment(invoice.paymentRequest);
|
|
|
|
if (!payResponse || !payResponse.preimage) {
|
|
|
|
showToast('error', 'NWC', 'Payment failed');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
showToast('success', 'NWC', 'Payment successful!');
|
|
|
|
|
|
|
|
try {
|
|
|
|
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
|
|
|
userId: session.user.id,
|
|
|
|
isSubscribed: true,
|
|
|
|
nwc: nwcInput,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (subscriptionResponse.status === 200) {
|
|
|
|
showToast('success', 'NWC', 'Subscription setup successful!');
|
|
|
|
if (onRecurringSubscriptionSuccess) onRecurringSubscriptionSuccess();
|
|
|
|
} else {
|
|
|
|
throw new Error('Unexpected response status');
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('Subscription setup error:', error);
|
|
|
|
showToast('error', 'NWC', 'Subscription setup failed. Please contact support.');
|
|
|
|
if (onError) onError(error);
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error('NWC error:', error);
|
|
|
|
showToast('error', 'NWC', `An error occurred: ${error.message}`);
|
|
|
|
if (onError) onError(error);
|
|
|
|
} finally {
|
|
|
|
setIsProcessing(false);
|
|
|
|
}
|
2024-08-30 19:37:13 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2024-08-31 18:18:21 -05:00
|
|
|
{!invoice && (
|
2024-09-25 20:57:14 -05:00
|
|
|
<div className={`w-full flex ${layout === "row" ? "flex-row justify-between" : "flex-col items-center"}`}>
|
2024-09-04 17:09:46 -05:00
|
|
|
{(oneTime || (!oneTime && !recurring)) && (
|
2024-09-10 15:44:08 -05:00
|
|
|
<GenericButton
|
2024-09-04 17:09:46 -05:00
|
|
|
label="Pay as you go"
|
|
|
|
icon="pi pi-bolt"
|
|
|
|
onClick={async () => {
|
2024-09-15 15:53:27 -05:00
|
|
|
if (status === 'unauthenticated') {
|
|
|
|
console.log('unauthenticated');
|
|
|
|
router.push('/auth/signin');
|
|
|
|
} else {
|
|
|
|
const invoice = await fetchInvoice();
|
|
|
|
setInvoice(invoice);
|
|
|
|
}
|
2024-09-04 17:09:46 -05:00
|
|
|
}}
|
|
|
|
severity='primary'
|
|
|
|
className="w-fit mt-4 text-[#f8f8ff]"
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{(recurring || (!oneTime && !recurring)) && (
|
2024-09-10 15:44:08 -05:00
|
|
|
<GenericButton
|
2024-09-04 17:09:46 -05:00
|
|
|
label="Setup Recurring Subscription"
|
|
|
|
icon={
|
|
|
|
<Image
|
|
|
|
src="/images/nwc-logo.svg"
|
|
|
|
alt="NWC Logo"
|
|
|
|
width={16}
|
|
|
|
height={16}
|
|
|
|
className="mr-2"
|
|
|
|
/>
|
|
|
|
}
|
|
|
|
severity='help'
|
|
|
|
className="w-fit mt-4 text-[#f8f8ff] bg-purple-600"
|
2024-09-15 15:53:27 -05:00
|
|
|
onClick={() => {
|
|
|
|
if (status === 'unauthenticated') {
|
|
|
|
console.log('unauthenticated');
|
|
|
|
router.push('/auth/signin');
|
|
|
|
} else {
|
|
|
|
setShowRecurringOptions(!showRecurringOptions);
|
|
|
|
}
|
|
|
|
}}
|
2024-09-04 17:09:46 -05:00
|
|
|
/>
|
|
|
|
)}
|
2024-08-31 18:18:21 -05:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
{showRecurringOptions && (
|
2024-08-31 21:52:50 -05:00
|
|
|
<>
|
|
|
|
<Divider />
|
|
|
|
<div className="w-fit mx-auto flex flex-col items-center mt-24">
|
2024-09-01 12:40:25 -05:00
|
|
|
<AlbyButton handleSubmit={handleRecurringSubscription} />
|
|
|
|
<span className='my-4 text-lg font-bold'>or</span>
|
|
|
|
<p className='text-lg font-bold'>Manually enter NWC URL</p>
|
|
|
|
<span className='text-sm text-gray-500'>*make sure you set a budget of at least 25000 sats and set budget renewal to monthly</span>
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
value={nwcInput}
|
|
|
|
onChange={(e) => setNwcInput(e.target.value)}
|
|
|
|
placeholder="Enter NWC URL"
|
|
|
|
className="w-full p-2 mb-4 border rounded"
|
|
|
|
/>
|
2024-09-10 15:44:08 -05:00
|
|
|
<GenericButton
|
2024-09-01 12:40:25 -05:00
|
|
|
label="Submit"
|
|
|
|
onClick={handleManualNwcSubmit}
|
|
|
|
className="mt-4 w-fit text-[#f8f8ff]"
|
|
|
|
/>
|
2024-08-31 21:52:50 -05:00
|
|
|
</div>
|
|
|
|
</>
|
2024-08-31 18:18:21 -05:00
|
|
|
)}
|
|
|
|
{invoice && invoice.paymentRequest && (
|
|
|
|
<div className="w-full mx-auto mt-8">
|
|
|
|
<PaymentModal
|
|
|
|
invoice={invoice?.paymentRequest}
|
|
|
|
onPaid={handlePaymentSuccess}
|
|
|
|
onError={handlePaymentError}
|
|
|
|
paymentMethods='external'
|
|
|
|
title={`Pay ${amount} sats`}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
2024-08-30 19:37:13 -05:00
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default SubscriptionPaymentButtons;
|