Payment flow for resources works

This commit is contained in:
austinkelsay 2024-08-12 17:27:47 -05:00
parent 573f560f28
commit 7808a88258
5 changed files with 95 additions and 51 deletions

View File

@ -3,6 +3,7 @@ import dynamic from 'next/dynamic';
import { initializeBitcoinConnect } from './BitcoinConnect';
import { LightningAddress } from '@getalby/lightning-tools';
import { useToast } from '@/hooks/useToast';
import { useSession } from 'next-auth/react';
import axios from 'axios'; // Import axios for API calls
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 [userId, setUserId] = useState(null);
const { showToast } = useToast();
const [pollingInterval, setPollingInterval] = useState(null);
const { data: session } = useSession();
useEffect(() => {
if (session?.user) {
setUserId(session.user.id);
}
}, [session]);
useEffect(() => {
initializeBitcoinConnect();
@ -77,7 +86,7 @@ const PaymentButton = ({ lnAddress, amount, onSuccess, onError, userId, resource
};
// 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) {
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;

View File

@ -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({
where: { id: userId },
data: {
purchased: {
create: purchaseData
}
create: {
resourceId: purchaseData.resourceId,
amountPaid: purchaseData.amountPaid,
},
},
},
include: {
purchased: true
}
purchased: {
include: {
resource: true,
},
},
},
});
};

View File

@ -20,6 +20,43 @@ const ndk = new NDK({
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({
providers: [
CredentialsProvider({
@ -30,46 +67,19 @@ export default NextAuth({
},
authorize: async (credentials) => {
if (credentials?.pubkey) {
await ndk.connect();
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 await authorize(credentials.pubkey);
}
return null;
},
}),
],
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
if (user) {
token.user = user;

View File

@ -1,11 +1,11 @@
import { addPurchaseToUser } from "@/db/models/userModels";
import { addResourcePurchaseToUser } from "@/db/models/userModels";
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const { userId, resourceId, amountPaid } = req.body;
const updatedUser = await addPurchaseToUser(userId, {
const updatedUser = await addResourcePurchaseToUser(userId, {
resourceId,
amountPaid: parseInt(amountPaid, 10) // Ensure amountPaid is an integer
});

View File

@ -48,7 +48,7 @@ export default function Details() {
const [authorView, setAuthorView] = useState(false);
const ndk = useNDKContext();
const { data: session, status } = useSession();
const { data: session, update } = useSession();
const [user, setUser] = useState(null);
const { returnImageProxy } = useImageProxy();
const { showToast } = useToast();
@ -81,12 +81,20 @@ export default function Details() {
useEffect(() => {
const decryptContent = async () => {
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
const decryptedContent = await nip04.decrypt(privkey, pubkey, processedEvent.content);
setDecryptedContent(decryptedContent);
}
}
}
decryptContent();
}, [user, paidResource, processedEvent]);
@ -211,12 +219,20 @@ export default function Details() {
return null;
};
const handlePaymentSuccess = (response) => {
console.log("response in higher level", response)
const handlePaymentSuccess = async (response, newResource) => {
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) => {
console.log("error in higher level", error)
showToast('error', 'Payment Error', `Failed to purchase resource. Please try again. Error: ${error}`);
}
return (
@ -267,10 +283,12 @@ export default function Details() {
amount={processedEvent.price}
onSuccess={handlePaymentSuccess}
onError={handlePaymentError}
userId={user.id} // Pass the user ID
resourceId={processedEvent.id} // Pass the course/resource ID
resourceId={processedEvent.d}
/>}
{/* 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} />
</div>
</div>