Add payment to cron endpoint for recurring subscribers, set cron to nce a day, set expiration to 31 days

This commit is contained in:
austinkelsay 2024-09-01 13:36:53 -05:00
parent e69d974ad7
commit b9c2d04ed4
5 changed files with 127 additions and 88 deletions

View File

@ -1,102 +1,106 @@
import prisma from "../prisma";
import { webln } from "@getalby/sdk";
import { LightningAddress } from "@getalby/sdk";
const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
export const getAllUsers = async () => {
return await prisma.user.findMany({
return await prisma.user.findMany({
include: {
role: true, // Include related role
purchased: {
include: {
role: true, // Include related role
purchased: {
include: {
course: true, // Include course details in purchases
resource: true, // Include resource details in purchases
},
},
course: true, // Include course details in purchases
resource: true, // Include resource details in purchases
},
});
},
},
});
};
export const getUserById = async (id) => {
return await prisma.user.findUnique({
where: { id },
return await prisma.user.findUnique({
where: { id },
include: {
role: true, // Include related role
purchased: {
include: {
role: true, // Include related role
purchased: {
include: {
course: true, // Include course details in purchases
resource: true, // Include resource details in purchases
},
},
course: true, // Include course details in purchases
resource: true, // Include resource details in purchases
},
});
},
},
});
};
export const getUserByPubkey = async (pubkey) => {
return await prisma.user.findUnique({
where: { pubkey },
return await prisma.user.findUnique({
where: { pubkey },
include: {
role: true, // Include related role
purchased: {
include: {
role: true, // Include related role
purchased: {
include: {
course: true, // Include course details in purchases
resource: true, // Include resource details in purchases
},
},
course: true, // Include course details in purchases
resource: true, // Include resource details in purchases
},
});
},
},
});
}
export const addResourcePurchaseToUser = async (userId, purchaseData) => {
return await prisma.user.update({
where: { id: userId },
data: {
purchased: {
create: {
resourceId: purchaseData.resourceId,
amountPaid: purchaseData.amountPaid,
},
return await prisma.user.update({
where: { id: userId },
data: {
purchased: {
create: {
resourceId: purchaseData.resourceId,
amountPaid: purchaseData.amountPaid,
},
},
include: {
purchased: {
include: {
resource: true,
},
},
include: {
purchased: {
include: {
resource: true,
},
},
});
};
},
});
};
export const addCoursePurchaseToUser = async (userId, purchaseData) => {
return await prisma.user.update({
where: { id: userId },
data: {
purchased: {
create: {
courseId: purchaseData.courseId,
amountPaid: purchaseData.amountPaid,
},
return await prisma.user.update({
where: { id: userId },
data: {
purchased: {
create: {
courseId: purchaseData.courseId,
amountPaid: purchaseData.amountPaid,
},
},
});
};
},
});
};
export const createUser = async (data) => {
return await prisma.user.create({
data,
});
return await prisma.user.create({
data,
});
};
export const updateUser = async (id, data) => {
console.log("user modelllll", id, data)
return await prisma.user.update({
where: { id },
data,
});
console.log("user modelllll", id, data)
return await prisma.user.update({
where: { id },
data,
});
};
export const deleteUser = async (id) => {
return await prisma.user.delete({
where: { id },
});
return await prisma.user.delete({
where: { id },
});
};
export const updateUserSubscription = async (userId, isSubscribed, nwc) => {
@ -129,24 +133,27 @@ export const updateUserSubscription = async (userId, isSubscribed, nwc) => {
});
};
export const checkAndUpdateExpiredSubscriptions = async () => {
export const findExpiredSubscriptions = async () => {
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 1 * 60 * 60 * 1000);
const fiveMinutesAgo = new Date(now.getTime() - 5 * 60 * 1000);
const thirtyOneDaysAgo = new Date(now.getTime() - 31 * 24 * 60 * 60 * 1000);
const expiredSubscriptions = await prisma.role.findMany({
where: {
return await prisma.role.findMany({
where: {
subscribed: true,
lastPaymentAt: {
lt: fiveMinutesAgo
lt: thirtyOneDaysAgo
}
},
select: {
userId: true
userId: true,
nwc: true
}
});
};
const updatePromises = expiredSubscriptions.map(({ userId }) =>
export const expireUserSubscriptions = async (userIds) => {
const now = new Date();
const updatePromises = userIds.map((userId) =>
prisma.role.update({
where: { userId },
data: {
@ -160,6 +167,5 @@ export const checkAndUpdateExpiredSubscriptions = async () => {
);
await prisma.$transaction(updatePromises);
return expiredSubscriptions.length;
return userIds.length;
};

View File

@ -1,4 +1,9 @@
import { checkAndUpdateExpiredSubscriptions } from "@/db/models/userModels";
import { findExpiredSubscriptions, updateUserSubscription, expireUserSubscriptions } from "@/db/models/userModels";
import { webln } from "@getalby/sdk";
import { LightningAddress } from '@getalby/lightning-tools';
const lnAddress = process.env.LIGHTNING_ADDRESS;
const amount = 25; // Set the subscription amount in satoshis
export default async function handler(req, res) {
if (req.headers.authorization !== `Bearer ${process.env.CRON_SECRET}`) {
@ -7,8 +12,41 @@ export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const updatedCount = await checkAndUpdateExpiredSubscriptions();
res.status(200).json({ message: `Cron job completed successfully. Updated ${updatedCount} subscriptions.` });
const expiredSubscriptions = await findExpiredSubscriptions();
const stillExpired = [];
for (const { userId, nwc } of expiredSubscriptions) {
if (nwc) {
try {
const nwcProvider = new webln.NostrWebLNProvider({
nostrWalletConnectUrl: nwc
});
await nwcProvider.enable();
const ln = new LightningAddress(lnAddress);
await ln.fetch();
const newInvoice = await ln.requestInvoice({ satoshi: amount });
const response = await nwcProvider.sendPayment(newInvoice?.paymentRequest);
if (response && response?.preimage) {
await updateUserSubscription(userId, true, nwc);
continue; // Skip adding to stillExpired list
}
} catch (error) {
console.error(`Payment failed for user ${userId}:`, error);
}
}
stillExpired.push(userId);
}
const expiredCount = await expireUserSubscriptions(stillExpired);
res.status(200).json({
message: `Cron job completed successfully.
Processed ${expiredSubscriptions.length} subscriptions.
Expired ${expiredCount} subscriptions.`
});
} catch (error) {
console.error('Cron job error:', error);
res.status(500).json({ error: error.message });

View File

@ -22,24 +22,19 @@ const Profile = () => {
const [subscribedUntil, setSubscribedUntil] = useState(null);
const [subscriptionExpiredAt, setSubscriptionExpiredAt] = useState(null);
const { data: session, status, update } = useSession();
const { data: session } = useSession();
const { returnImageProxy } = useImageProxy();
const { ndk } = useNDKContext();
const menu = useRef(null);
useEffect(() => {
// Refetch the session when the component mounts
update();
}, []);
useEffect(() => {
if (session && session.user) {
setUser(session.user);
if (session.user.role) {
setSubscribed(session.user.role.subscribed);
const subscribedAt = new Date(session.user.role.lastPaymentAt);
// The user is subscribed until the date in subscribedAt + 30 days
const subscribedUntil = new Date(subscribedAt.getTime() + 30 * 24 * 60 * 60 * 1000);
// The user is subscribed until the date in subscribedAt + 31 days
const subscribedUntil = new Date(subscribedAt.getTime() + 31 * 24 * 60 * 60 * 1000);
setSubscribedUntil(subscribedUntil);
if (session.user.role.subscriptionExpiredAt) {
const expiredAt = new Date(session.user.role.subscriptionExpiredAt)

View File

@ -7,7 +7,7 @@ export const findKind0Fields = async (kind0) => {
const findTruthyPropertyValue = (object, properties) => {
for (const property of properties) {
if (object[property]) {
if (object?.[property]) {
return object[property];
}
}

View File

@ -3,7 +3,7 @@
"crons": [
{
"path": "/api/users/subscription/cron",
"schedule": "0 * * * *"
"schedule": "0 0 * * *"
}
]
}