mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-01 14:52:02 +00:00
354 lines
12 KiB
JavaScript
354 lines
12 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { track } from '@vercel/analytics';
|
|
import { initializeBitcoinConnect, getSDK } from './BitcoinConnect';
|
|
import { LightningAddress } from '@getalby/lightning-tools';
|
|
import { useToast } from '@/hooks/useToast';
|
|
import { useSession } from 'next-auth/react';
|
|
import { useRouter } from 'next/router';
|
|
import { Divider } from 'primereact/divider';
|
|
import dynamic from 'next/dynamic';
|
|
import AlbyButton from '@/components/buttons/AlbyButton';
|
|
import GenericButton from '@/components/buttons/GenericButton';
|
|
import axios from 'axios';
|
|
import Image from 'next/image';
|
|
|
|
const PaymentModal = dynamic(
|
|
() => import('@getalby/bitcoin-connect-react').then(mod => mod.Payment),
|
|
{ ssr: false }
|
|
);
|
|
|
|
const SubscriptionPaymentButtons = ({
|
|
onSuccess,
|
|
onError,
|
|
onRecurringSubscriptionSuccess,
|
|
setIsProcessing,
|
|
oneTime = false,
|
|
recurring = false,
|
|
layout = 'row',
|
|
subscriptionType = 'monthly',
|
|
}) => {
|
|
const [invoice, setInvoice] = useState(null);
|
|
const [showRecurringOptions, setShowRecurringOptions] = useState(false);
|
|
const [nwcInput, setNwcInput] = useState('');
|
|
const { showToast } = useToast();
|
|
const { data: session, status } = useSession();
|
|
const router = useRouter();
|
|
|
|
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
|
|
|
|
// Calculate the amount based on the subscription type
|
|
const getAmount = () => {
|
|
return subscriptionType === 'yearly' ? 500000 : 50000;
|
|
};
|
|
|
|
const amount = getAmount();
|
|
|
|
useEffect(() => {
|
|
// Initialize Bitcoin Connect as early as possible
|
|
initializeBitcoinConnect().catch(err => {
|
|
console.error("Error initializing Bitcoin Connect:", err);
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
let intervalId;
|
|
if (invoice) {
|
|
intervalId = setInterval(async () => {
|
|
try {
|
|
const paid = await invoice.verifyPayment();
|
|
if (paid) {
|
|
clearInterval(intervalId);
|
|
// handle success
|
|
onSuccess();
|
|
}
|
|
} catch (error) {
|
|
console.error("Error verifying payment:", error);
|
|
clearInterval(intervalId);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
return () => {
|
|
if (intervalId) {
|
|
clearInterval(intervalId);
|
|
}
|
|
};
|
|
}, [invoice, onSuccess]);
|
|
|
|
const fetchInvoice = async () => {
|
|
try {
|
|
const ln = new LightningAddress(lnAddress);
|
|
await ln.fetch();
|
|
const newInvoice = await ln.requestInvoice({
|
|
satoshi: amount,
|
|
comment: `${subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1)} Subscription Purchase. User: ${session?.user?.id}`,
|
|
});
|
|
return newInvoice;
|
|
} catch (error) {
|
|
console.error('Error fetching invoice:', error);
|
|
showToast('error', 'Invoice Error', `Failed to fetch the invoice: ${error.message}`);
|
|
if (onError) onError(error);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const handlePaymentSuccess = async response => {
|
|
track('Subscription Payment', { method: 'pay_as_you_go', type: subscriptionType, userId: session?.user?.id });
|
|
showToast('success', 'Payment Successful', 'Your payment has been processed successfully.');
|
|
if (onSuccess) onSuccess(response);
|
|
};
|
|
|
|
const handlePaymentError = async error => {
|
|
console.error('Payment error', error);
|
|
showToast('error', 'Payment Failed', `An error occurred during payment: ${error.message}`);
|
|
if (onError) onError(error);
|
|
};
|
|
|
|
const handleRecurringSubscription = async () => {
|
|
if (!setIsProcessing) {
|
|
console.warn("setIsProcessing is not defined");
|
|
} else {
|
|
setIsProcessing(true);
|
|
}
|
|
|
|
try {
|
|
// Get SDK directly to avoid client issues
|
|
const sdk = await getSDK();
|
|
|
|
// Create NWC client
|
|
const newNwc = sdk.nwc.NWCClient.withNewSecret();
|
|
|
|
const yearFromNow = new Date();
|
|
yearFromNow.setFullYear(yearFromNow.getFullYear() + 1);
|
|
|
|
const initNwcOptions = {
|
|
name: 'plebdevs.com',
|
|
requestMethods: ['pay_invoice'],
|
|
maxAmount: amount,
|
|
editable: false,
|
|
budgetRenewal: subscriptionType === 'yearly' ? 'yearly' : 'monthly',
|
|
expiresAt: yearFromNow,
|
|
};
|
|
|
|
console.log("Initializing NWC with options:", initNwcOptions);
|
|
|
|
// Initialize NWC
|
|
await newNwc.initNWC(initNwcOptions);
|
|
showToast('info', 'Alby', 'Alby connection window opened.');
|
|
|
|
// Get NWC URL
|
|
const newNWCUrl = newNwc.getNostrWalletConnectUrl();
|
|
|
|
if (newNWCUrl) {
|
|
const nwcProvider = new sdk.webln.NostrWebLNProvider({
|
|
nostrWalletConnectUrl: newNWCUrl,
|
|
});
|
|
|
|
await nwcProvider.enable();
|
|
console.log("NWC provider enabled");
|
|
|
|
const invoice = await fetchInvoice();
|
|
console.log("Invoice fetched for recurring payment:", !!invoice);
|
|
|
|
if (!invoice || !invoice.paymentRequest) {
|
|
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
|
if (setIsProcessing) setIsProcessing(false);
|
|
return;
|
|
}
|
|
|
|
console.log("Sending payment with NWC provider");
|
|
const paymentResponse = await nwcProvider.sendPayment(invoice.paymentRequest);
|
|
console.log("Payment response:", paymentResponse?.preimage);
|
|
|
|
if (!paymentResponse || !paymentResponse?.preimage) {
|
|
showToast('error', 'NWC', 'Payment failed');
|
|
if (setIsProcessing) setIsProcessing(false);
|
|
return;
|
|
}
|
|
|
|
console.log("Updating subscription in API");
|
|
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
|
userId: session.user.id,
|
|
isSubscribed: true,
|
|
nwc: newNWCUrl,
|
|
subscriptionType: subscriptionType,
|
|
});
|
|
|
|
if (subscriptionResponse.status === 200) {
|
|
track('Subscription Payment', { method: 'recurring', type: subscriptionType, userId: session?.user?.id });
|
|
showToast('success', 'Subscription Setup', 'Recurring subscription setup successful!');
|
|
if (onRecurringSubscriptionSuccess) onRecurringSubscriptionSuccess();
|
|
} else {
|
|
throw new Error(`Unexpected response status: ${subscriptionResponse.status}`);
|
|
}
|
|
} 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 {
|
|
if (setIsProcessing) setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
const handleManualNwcSubmit = async () => {
|
|
if (!nwcInput) {
|
|
showToast('error', 'NWC', 'Please enter a valid NWC URL');
|
|
return;
|
|
}
|
|
|
|
if (setIsProcessing) setIsProcessing(true);
|
|
|
|
try {
|
|
const sdk = await getSDK();
|
|
const nwc = new sdk.webln.NostrWebLNProvider({
|
|
nostrWalletConnectUrl: nwcInput,
|
|
});
|
|
|
|
await nwc.enable();
|
|
console.log("Manual NWC provider enabled");
|
|
|
|
const invoice = await fetchInvoice();
|
|
console.log("Invoice fetched for manual NWC:", !!invoice);
|
|
|
|
if (!invoice || !invoice.paymentRequest) {
|
|
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
|
if (setIsProcessing) setIsProcessing(false);
|
|
return;
|
|
}
|
|
|
|
console.log("Sending payment with manual NWC");
|
|
const payResponse = await nwc.sendPayment(invoice.paymentRequest);
|
|
console.log("Payment response:", payResponse?.preimage);
|
|
|
|
if (!payResponse || !payResponse.preimage) {
|
|
showToast('error', 'NWC', 'Payment failed');
|
|
if (setIsProcessing) setIsProcessing(false);
|
|
return;
|
|
}
|
|
|
|
showToast('success', 'NWC', 'Payment successful!');
|
|
|
|
try {
|
|
console.log("Updating subscription in API (manual)");
|
|
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
|
userId: session.user.id,
|
|
isSubscribed: true,
|
|
nwc: nwcInput,
|
|
subscriptionType: subscriptionType,
|
|
});
|
|
|
|
if (subscriptionResponse.status === 200) {
|
|
track('Subscription Payment', { method: 'recurring-manual', type: subscriptionType, userId: session?.user?.id });
|
|
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 {
|
|
if (setIsProcessing) setIsProcessing(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{!invoice && (
|
|
<div
|
|
className={`w-full flex ${layout === 'row' ? 'flex-row justify-between' : 'flex-col items-center gap-4'}`}
|
|
>
|
|
{(oneTime || (!oneTime && !recurring)) && (
|
|
<GenericButton
|
|
label={`Pay as you go (${(amount).toLocaleString()} sats)`}
|
|
icon="pi pi-bolt"
|
|
onClick={async () => {
|
|
if (status === 'unauthenticated') {
|
|
console.log('unauthenticated');
|
|
router.push('/auth/signin');
|
|
} else {
|
|
const invoice = await fetchInvoice();
|
|
setInvoice(invoice);
|
|
}
|
|
}}
|
|
severity="primary"
|
|
className={`mt-4 text-[#f8f8ff] ${layout === 'col' ? 'w-full max-w-md' : 'w-fit'}`}
|
|
/>
|
|
)}
|
|
{(recurring || (!oneTime && !recurring)) && (
|
|
<GenericButton
|
|
label={`Setup Recurring ${subscriptionType ? (subscriptionType.charAt(0).toUpperCase() + subscriptionType.slice(1)) : 'Monthly'} Subscription`}
|
|
icon={
|
|
<Image
|
|
src="/images/nwc-logo.svg"
|
|
alt="NWC Logo"
|
|
width={16}
|
|
height={16}
|
|
className="mr-2"
|
|
/>
|
|
}
|
|
severity="help"
|
|
className={`mt-4 text-[#f8f8ff] bg-purple-600 ${layout === 'col' ? 'w-full max-w-md' : 'w-fit'}`}
|
|
onClick={() => {
|
|
if (status === 'unauthenticated') {
|
|
console.log('unauthenticated');
|
|
router.push('/auth/signin');
|
|
} else {
|
|
setShowRecurringOptions(!showRecurringOptions);
|
|
}
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
)}
|
|
{showRecurringOptions && (
|
|
<>
|
|
<Divider />
|
|
<div className="w-fit mx-auto flex flex-col items-center mt-24">
|
|
<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 {(amount).toLocaleString()} sats and set budget renewal to {subscriptionType}
|
|
</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"
|
|
/>
|
|
<GenericButton
|
|
label="Submit"
|
|
onClick={handleManualNwcSubmit}
|
|
className="mt-4 w-fit text-[#f8f8ff]"
|
|
/>
|
|
</div>
|
|
</>
|
|
)}
|
|
{invoice && invoice.paymentRequest && (
|
|
<div className="w-full mx-auto mt-8">
|
|
<PaymentModal
|
|
invoice={invoice?.paymentRequest}
|
|
onPaid={handlePaymentSuccess}
|
|
onError={handlePaymentError}
|
|
paymentMethods="external"
|
|
title={`Pay ${(amount).toLocaleString()} sats`}
|
|
/>
|
|
</div>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SubscriptionPaymentButtons;
|