expiry-using-payment #79
@ -13,7 +13,6 @@ interface MembershipData {
|
|||||||
subscription?: {
|
subscription?: {
|
||||||
name: string;
|
name: string;
|
||||||
frequency: string;
|
frequency: string;
|
||||||
assignedAt: admin.firestore.Timestamp;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,6 +22,17 @@ interface ClientFields {
|
|||||||
"last-name"?: string;
|
"last-name"?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PaymentData {
|
||||||
|
id: string;
|
||||||
|
date: string;
|
||||||
|
amount: number;
|
||||||
|
paymentMethod: string;
|
||||||
|
referenceNumber: string;
|
||||||
|
dateTimestamp: Date;
|
||||||
|
createdAt: Date;
|
||||||
|
discount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const checkExpiredMemberships = onSchedule(
|
export const checkExpiredMemberships = onSchedule(
|
||||||
{
|
{
|
||||||
schedule: "0 8,14,20 * * *",
|
schedule: "0 8,14,20 * * *",
|
||||||
@ -72,11 +82,28 @@ async function findExpiredMemberships(): Promise<
|
|||||||
|
|
||||||
const expired: Array<{ id: string; data: MembershipData }> = [];
|
const expired: Array<{ id: string; data: MembershipData }> = [];
|
||||||
|
|
||||||
snapshot.docs.forEach((doc) => {
|
const batchSize = 10;
|
||||||
const data = doc.data() as MembershipData;
|
const docs = snapshot.docs;
|
||||||
const isExpired = checkIfMembershipExpired(data);
|
|
||||||
if (isExpired) expired.push({ id: doc.id, data });
|
for (let i = 0; i < docs.length; i += batchSize) {
|
||||||
});
|
const batch = docs.slice(i, i + batchSize);
|
||||||
|
const batchResults = await Promise.allSettled(
|
||||||
|
batch.map(async (doc) => {
|
||||||
|
const data = doc.data() as MembershipData;
|
||||||
|
const isExpired = await checkIfMembershipExpired(doc.id, data);
|
||||||
|
if (isExpired) {
|
||||||
|
return { id: doc.id, data };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
batchResults.forEach((result) => {
|
||||||
|
if (result.status === "fulfilled" && result.value) {
|
||||||
|
expired.push(result.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return expired;
|
return expired;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -85,35 +112,103 @@ async function findExpiredMemberships(): Promise<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkIfMembershipExpired(data: MembershipData): boolean {
|
async function checkIfMembershipExpired(
|
||||||
|
membershipId: string,
|
||||||
|
data: MembershipData
|
||||||
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
if (
|
if (!data.subscription || !data.subscription.frequency) {
|
||||||
!data.subscription ||
|
|
||||||
!data.subscription.frequency ||
|
|
||||||
!data.subscription.assignedAt
|
|
||||||
) {
|
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Skipping expiry check for membership ${data.id} with missing subscription data.`
|
`Skipping expiry check for membership ${membershipId} with missing subscription data.`
|
||||||
);
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const startDate = (
|
let startDate: Date;
|
||||||
data.subscription.assignedAt as admin.firestore.Timestamp
|
|
||||||
).toDate();
|
const payments = await getPaymentsForMembership(membershipId);
|
||||||
|
if (payments.length === 0) {
|
||||||
|
logger.warn(
|
||||||
|
`No payments found for membership ${membershipId}, cannot determine expiry`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestPayment = payments[0];
|
||||||
|
startDate = latestPayment.dateTimestamp;
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Using latest payment date ${startDate.toISOString()} for membership ${membershipId}`
|
||||||
|
);
|
||||||
|
|
||||||
const expiryDate = calculateExpiryDate(
|
const expiryDate = calculateExpiryDate(
|
||||||
startDate,
|
startDate,
|
||||||
data.subscription.frequency
|
data.subscription.frequency
|
||||||
);
|
);
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
return now > expiryDate;
|
const isExpired = now > expiryDate;
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
logger.info(
|
||||||
|
`Membership ${membershipId} expired on ${expiryDate.toISOString()}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isExpired;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error checking expiry for membership ${data.id}:`, error);
|
logger.error(
|
||||||
|
`Error checking expiry for membership ${membershipId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getPaymentsForMembership(
|
||||||
|
membershipId: string
|
||||||
|
): Promise<PaymentData[]> {
|
||||||
|
try {
|
||||||
|
const docSnapshot = await app
|
||||||
|
.firestore()
|
||||||
|
.collection("membershipPayments")
|
||||||
|
.doc(membershipId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (!docSnapshot.exists) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = docSnapshot.data();
|
||||||
|
const paymentsData = data?.payments || [];
|
||||||
|
|
||||||
|
const payments: PaymentData[] = paymentsData.map((payment: any) => ({
|
||||||
|
id: payment.id,
|
||||||
|
date: payment.date,
|
||||||
|
amount: payment.amount,
|
||||||
|
paymentMethod: payment.paymentMethod,
|
||||||
|
referenceNumber: payment.referenceNumber,
|
||||||
|
dateTimestamp: payment.dateTimestamp.toDate
|
||||||
|
? payment.dateTimestamp.toDate()
|
||||||
|
: new Date(payment.dateTimestamp),
|
||||||
|
createdAt: payment.createdAt.toDate
|
||||||
|
? payment.createdAt.toDate()
|
||||||
|
: new Date(payment.createdAt),
|
||||||
|
discount: payment.discount,
|
||||||
|
}));
|
||||||
|
|
||||||
|
payments.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
||||||
|
|
||||||
|
return payments;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`Error getting payments for membership ${membershipId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function calculateExpiryDate(startDate: Date, frequency: string): Date {
|
function calculateExpiryDate(startDate: Date, frequency: string): Date {
|
||||||
const expiry = new Date(startDate);
|
const expiry = new Date(startDate);
|
||||||
switch (frequency.toLowerCase()) {
|
switch (frequency.toLowerCase()) {
|
||||||
@ -135,6 +230,33 @@ function calculateExpiryDate(startDate: Date, frequency: string): Date {
|
|||||||
return expiry;
|
return expiry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function calculateRenewalDateFromPayment(
|
||||||
|
subscription: any,
|
||||||
|
paymentDate: Date
|
||||||
|
): Date {
|
||||||
|
const renewalDate = new Date(paymentDate);
|
||||||
|
const frequency = subscription.frequency || "Monthly";
|
||||||
|
|
||||||
|
switch (frequency.toLowerCase()) {
|
||||||
|
case "monthly":
|
||||||
|
renewalDate.setMonth(renewalDate.getMonth() + 1);
|
||||||
|
break;
|
||||||
|
case "quarterly":
|
||||||
|
renewalDate.setMonth(renewalDate.getMonth() + 3);
|
||||||
|
break;
|
||||||
|
case "half-yearly":
|
||||||
|
renewalDate.setMonth(renewalDate.getMonth() + 6);
|
||||||
|
break;
|
||||||
|
case "yearly":
|
||||||
|
renewalDate.setFullYear(renewalDate.getFullYear() + 1);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
renewalDate.setMonth(renewalDate.getMonth() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return renewalDate;
|
||||||
|
}
|
||||||
|
|
||||||
async function processExpiredMembership(
|
async function processExpiredMembership(
|
||||||
membershipId: string,
|
membershipId: string,
|
||||||
membershipData: MembershipData
|
membershipData: MembershipData
|
||||||
@ -174,14 +296,22 @@ async function sendPlanExpiredNotification(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expiryDate = membershipData.subscription?.assignedAt?.toDate();
|
let expiryDate: Date | undefined;
|
||||||
const formattedDate = expiryDate
|
let formattedDate = "Unknown Date";
|
||||||
? expiryDate.toLocaleDateString("en-US", {
|
|
||||||
year: "numeric",
|
const payments = await getPaymentsForMembership(membershipId);
|
||||||
month: "long",
|
if (payments.length > 0) {
|
||||||
day: "numeric",
|
const latestPayment = payments[0];
|
||||||
})
|
expiryDate = calculateRenewalDateFromPayment(
|
||||||
: "Unknown Date";
|
membershipData.subscription,
|
||||||
|
latestPayment.dateTimestamp
|
||||||
|
);
|
||||||
|
formattedDate = expiryDate.toLocaleDateString("en-US", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await app
|
await app
|
||||||
.firestore()
|
.firestore()
|
||||||
@ -203,9 +333,9 @@ async function sendPlanExpiredNotification(
|
|||||||
membershipId,
|
membershipId,
|
||||||
gymName,
|
gymName,
|
||||||
formattedExpiryDate: formattedDate,
|
formattedExpiryDate: formattedDate,
|
||||||
expiryDate:
|
expiryDate: expiryDate
|
||||||
membershipData.subscription?.assignedAt ||
|
? admin.firestore.Timestamp.fromDate(expiryDate)
|
||||||
admin.firestore.Timestamp.fromDate(new Date()),
|
: admin.firestore.Timestamp.fromDate(new Date()),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user