mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
Payment flow for resources works
This commit is contained in:
parent
573f560f28
commit
7808a88258
@ -3,6 +3,7 @@ import dynamic from 'next/dynamic';
|
|||||||
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 axios from 'axios'; // Import axios for API calls
|
import axios from 'axios'; // Import axios for API calls
|
||||||
|
|
||||||
const PayButton = dynamic(
|
const PayButton = dynamic(
|
||||||
@ -12,10 +13,18 @@ const PayButton = dynamic(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const PaymentButton = ({ lnAddress, amount, onSuccess, onError, userId, resourceId }) => {
|
const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resourceId }) => {
|
||||||
const [invoice, setInvoice] = useState(null);
|
const [invoice, setInvoice] = useState(null);
|
||||||
|
const [userId, setUserId] = useState(null);
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const [pollingInterval, setPollingInterval] = useState(null);
|
const [pollingInterval, setPollingInterval] = useState(null);
|
||||||
|
const { data: session } = useSession();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (session?.user) {
|
||||||
|
setUserId(session.user.id);
|
||||||
|
}
|
||||||
|
}, [session]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeBitcoinConnect();
|
initializeBitcoinConnect();
|
||||||
@ -77,7 +86,7 @@ const PaymentButton = ({ lnAddress, amount, onSuccess, onError, userId, resource
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Make an API call to add the purchase to the user
|
// Make an API call to add the purchase to the user
|
||||||
const result = await axios.post('/api/purchases', purchaseData);
|
const result = await axios.post('/api/purchase/resource', purchaseData);
|
||||||
|
|
||||||
if (result.status === 200) {
|
if (result.status === 200) {
|
||||||
showToast('success', 'Payment Successful', `Paid ${amount} sats and updated user purchases`);
|
showToast('success', 'Payment Successful', `Paid ${amount} sats and updated user purchases`);
|
||||||
@ -138,4 +147,4 @@ const PaymentButton = ({ lnAddress, amount, onSuccess, onError, userId, resource
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PaymentButton;
|
export default ResourcePaymentButton;
|
||||||
|
@ -44,17 +44,24 @@ export const getUserByPubkey = async (pubkey) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addPurchaseToUser = async (userId, purchaseData) => {
|
export const addResourcePurchaseToUser = async (userId, purchaseData) => {
|
||||||
return await prisma.user.update({
|
return await prisma.user.update({
|
||||||
where: { id: userId },
|
where: { id: userId },
|
||||||
data: {
|
data: {
|
||||||
purchased: {
|
purchased: {
|
||||||
create: purchaseData
|
create: {
|
||||||
}
|
resourceId: purchaseData.resourceId,
|
||||||
|
amountPaid: purchaseData.amountPaid,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
purchased: true
|
purchased: {
|
||||||
}
|
include: {
|
||||||
|
resource: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -20,6 +20,43 @@ const ndk = new NDK({
|
|||||||
explicitRelayUrls: relayUrls,
|
explicitRelayUrls: relayUrls,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const authorize = async (pubkey) => {
|
||||||
|
await ndk.connect();
|
||||||
|
const user = ndk.getUser({ pubkey });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const profile = await user.fetchProfile();
|
||||||
|
|
||||||
|
// Check if user exists, create if not
|
||||||
|
const response = await axios.get(`${BASE_URL}/api/users/${pubkey}`);
|
||||||
|
if (response.status === 200 && response.data) {
|
||||||
|
const fields = await findKind0Fields(profile);
|
||||||
|
|
||||||
|
// Combine user object with kind0Fields, giving priority to kind0Fields
|
||||||
|
const combinedUser = { ...fields, ...response.data };
|
||||||
|
|
||||||
|
// Update the user on the backend if necessary
|
||||||
|
// await axios.put(`${BASE_URL}/api/users/${combinedUser.id}`, combinedUser);
|
||||||
|
|
||||||
|
return combinedUser;
|
||||||
|
} else if (response.status === 204) {
|
||||||
|
// Create user
|
||||||
|
if (profile) {
|
||||||
|
const fields = await findKind0Fields(profile);
|
||||||
|
console.log('FEEEEELDS', fields);
|
||||||
|
const payload = { pubkey, ...fields };
|
||||||
|
|
||||||
|
const createUserResponse = await axios.post(`${BASE_URL}/api/users`, payload);
|
||||||
|
return createUserResponse.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Nostr login error:", error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default NextAuth({
|
export default NextAuth({
|
||||||
providers: [
|
providers: [
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
@ -30,46 +67,19 @@ export default NextAuth({
|
|||||||
},
|
},
|
||||||
authorize: async (credentials) => {
|
authorize: async (credentials) => {
|
||||||
if (credentials?.pubkey) {
|
if (credentials?.pubkey) {
|
||||||
await ndk.connect();
|
return await authorize(credentials.pubkey);
|
||||||
|
|
||||||
const user = ndk.getUser({ pubkey: credentials.pubkey });
|
|
||||||
|
|
||||||
try {
|
|
||||||
const profile = await user.fetchProfile();
|
|
||||||
|
|
||||||
// Check if user exists, create if not
|
|
||||||
const response = await axios.get(`${BASE_URL}/api/users/${credentials.pubkey}`);
|
|
||||||
if (response.status === 200 && response.data) {
|
|
||||||
const fields = await findKind0Fields(profile);
|
|
||||||
|
|
||||||
// Combine user object with kind0Fields, giving priority to kind0Fields
|
|
||||||
const combinedUser = { ...fields, ...response.data };
|
|
||||||
|
|
||||||
// Update the user on the backend if necessary
|
|
||||||
// await axios.put(`${BASE_URL}/api/users/${combinedUser.id}`, combinedUser);
|
|
||||||
|
|
||||||
return combinedUser;
|
|
||||||
} else if (response.status === 204) {
|
|
||||||
// Create user
|
|
||||||
if (profile) {
|
|
||||||
const fields = await findKind0Fields(profile);
|
|
||||||
console.log('FEEEEELDS', fields);
|
|
||||||
const payload = { pubkey: credentials.pubkey, ...fields };
|
|
||||||
|
|
||||||
const createUserResponse = await axios.post(`${BASE_URL}/api/users`, payload);
|
|
||||||
return createUserResponse.data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Nostr login error:", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, user }) {
|
async jwt({ token, trigger, user }) {
|
||||||
|
if (trigger === "update") {
|
||||||
|
// if we trigger an update call the authorize function again
|
||||||
|
const newUser = await authorize(token.user.pubkey);
|
||||||
|
token.user = newUser;
|
||||||
|
}
|
||||||
// Add combined user object to the token
|
// Add combined user object to the token
|
||||||
if (user) {
|
if (user) {
|
||||||
token.user = user;
|
token.user = user;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { addPurchaseToUser } from "@/db/models/userModels";
|
import { addResourcePurchaseToUser } from "@/db/models/userModels";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
try {
|
try {
|
||||||
const { userId, resourceId, amountPaid } = req.body;
|
const { userId, resourceId, amountPaid } = req.body;
|
||||||
|
|
||||||
const updatedUser = await addPurchaseToUser(userId, {
|
const updatedUser = await addResourcePurchaseToUser(userId, {
|
||||||
resourceId,
|
resourceId,
|
||||||
amountPaid: parseInt(amountPaid, 10) // Ensure amountPaid is an integer
|
amountPaid: parseInt(amountPaid, 10) // Ensure amountPaid is an integer
|
||||||
});
|
});
|
@ -48,7 +48,7 @@ export default function Details() {
|
|||||||
const [authorView, setAuthorView] = useState(false);
|
const [authorView, setAuthorView] = useState(false);
|
||||||
|
|
||||||
const ndk = useNDKContext();
|
const ndk = useNDKContext();
|
||||||
const { data: session, status } = useSession();
|
const { data: session, update } = useSession();
|
||||||
const [user, setUser] = useState(null);
|
const [user, setUser] = useState(null);
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
@ -81,12 +81,20 @@ export default function Details() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const decryptContent = async () => {
|
const decryptContent = async () => {
|
||||||
if (user && paidResource) {
|
if (user && paidResource) {
|
||||||
if (user?.purchased?.includes(processedEvent.id) || (user?.role && user?.role.subscribed)) {
|
if (user?.purchased?.length > 0) {
|
||||||
|
const purchasedResource = user?.purchased.find(purchase => purchase.resourceId === processedEvent.d);
|
||||||
|
if (purchasedResource) {
|
||||||
|
console.log("purchasedResource", purchasedResource)
|
||||||
|
const decryptedContent = await nip04.decrypt(privkey, pubkey, processedEvent.content);
|
||||||
|
setDecryptedContent(decryptedContent);
|
||||||
|
}
|
||||||
|
} else if (user?.role && user?.role.subscribed) {
|
||||||
// decrypt the content
|
// decrypt the content
|
||||||
const decryptedContent = await nip04.decrypt(privkey, pubkey, processedEvent.content);
|
const decryptedContent = await nip04.decrypt(privkey, pubkey, processedEvent.content);
|
||||||
setDecryptedContent(decryptedContent);
|
setDecryptedContent(decryptedContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
decryptContent();
|
decryptContent();
|
||||||
}, [user, paidResource, processedEvent]);
|
}, [user, paidResource, processedEvent]);
|
||||||
@ -211,12 +219,20 @@ export default function Details() {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePaymentSuccess = (response) => {
|
const handlePaymentSuccess = async (response, newResource) => {
|
||||||
console.log("response in higher level", response)
|
if (response && response?.preimage) {
|
||||||
|
console.log("newResource", newResource)
|
||||||
|
// Refetch session to get the latest user data
|
||||||
|
const updated = await update();
|
||||||
|
console.log("session after update", updated)
|
||||||
|
// router.reload(); // Optionally, reload the page if necessary
|
||||||
|
} else {
|
||||||
|
showToast('error', 'Error', 'Failed to purchase resource. Please try again.');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePaymentError = (error) => {
|
const handlePaymentError = (error) => {
|
||||||
console.log("error in higher level", error)
|
showToast('error', 'Payment Error', `Failed to purchase resource. Please try again. Error: ${error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -267,10 +283,12 @@ export default function Details() {
|
|||||||
amount={processedEvent.price}
|
amount={processedEvent.price}
|
||||||
onSuccess={handlePaymentSuccess}
|
onSuccess={handlePaymentSuccess}
|
||||||
onError={handlePaymentError}
|
onError={handlePaymentError}
|
||||||
userId={user.id} // Pass the user ID
|
resourceId={processedEvent.d}
|
||||||
resourceId={processedEvent.id} // Pass the course/resource ID
|
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
|
{/* if the resource has been paid for show a green paid x sats text */}
|
||||||
|
{paidResource && decryptedContent && <p className='text-green-500'>Paid {processedEvent.price} sats</p>}
|
||||||
|
|
||||||
<ZapDisplay zapAmount={zapAmount} event={processedEvent} zapsLoading={zapsLoading} />
|
<ZapDisplay zapAmount={zapAmount} event={processedEvent} zapsLoading={zapsLoading} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user