const EmailClient = require("../core/EmailClient"); const TemplateManager = require("../core/TemplateManager"); const logger = require("../../../utils/logger"); /** * RentalFlowEmailService handles rental lifecycle flow emails * This service is responsible for: * - Sending rental request notifications to owners * - Sending rental request confirmations to renters * - Sending rental approval confirmations to owners * - Sending rental declined notifications to renters * - Sending rental confirmation emails to renters and owners * - Sending rental cancellation emails to both parties * - Sending rental completion emails to both parties * - Sending payout received notifications to owners */ class RentalFlowEmailService { constructor() { this.emailClient = new EmailClient(); this.templateManager = new TemplateManager(); this.initialized = false; } /** * Initialize the rental flow email service * @returns {Promise} */ async initialize() { if (this.initialized) return; await Promise.all([ this.emailClient.initialize(), this.templateManager.initialize(), ]); this.initialized = true; console.log("Rental Flow Email Service initialized successfully"); } /** * Send rental request email to owner * @param {Object} owner - Owner user object * @param {string} owner.email - Owner's email address * @param {string} owner.firstName - Owner's first name * @param {Object} renter - Renter user object * @param {string} renter.firstName - Renter's first name * @param {string} renter.lastName - Renter's last name * @param {Object} rental - Rental object with all details * @param {number} rental.id - Rental ID * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.payoutAmount - Owner's payout amount * @param {string} rental.deliveryMethod - Delivery method * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendRentalRequestEmail(owner, renter, rental) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const approveUrl = `${frontendUrl}/owning?rentalId=${rental.id}`; const variables = { ownerName: owner.firstName, renterName: `${renter.firstName} ${renter.lastName}`.trim() || "A renter", itemName: rental.item?.name || "your item", startDate: rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", endDate: rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", totalAmount: rental.totalAmount ? parseFloat(rental.totalAmount).toFixed(2) : "0.00", payoutAmount: rental.payoutAmount ? parseFloat(rental.payoutAmount).toFixed(2) : "0.00", deliveryMethod: rental.deliveryMethod || "Not specified", intendedUse: rental.intendedUse || "Not specified", approveUrl: approveUrl, }; const htmlContent = await this.templateManager.renderTemplate( "rentalRequestToOwner", variables ); return await this.emailClient.sendEmail( owner.email, `Rental Request for ${rental.item?.name || "Your Item"}`, htmlContent ); } catch (error) { console.error("Failed to send rental request email:", error); return { success: false, error: error.message }; } } /** * Send rental request confirmation email to renter * @param {Object} renter - Renter user object * @param {string} renter.email - Renter's email address * @param {string} renter.firstName - Renter's first name * @param {Object} rental - Rental object with all details * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.deliveryMethod - Delivery method * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendRentalRequestConfirmationEmail(renter, rental) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const viewRentalsUrl = `${frontendUrl}/renting`; // Determine payment message based on rental amount const totalAmount = parseFloat(rental.totalAmount) || 0; const paymentMessage = totalAmount > 0 ? "The owner will review your request. You'll only be charged if they approve it." : "The owner will review your request and respond soon."; const variables = { renterName: renter.firstName || "there", itemName: rental.item?.name || "the item", startDate: rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", endDate: rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", totalAmount: totalAmount.toFixed(2), deliveryMethod: rental.deliveryMethod || "Not specified", paymentMessage: paymentMessage, viewRentalsUrl: viewRentalsUrl, }; const htmlContent = await this.templateManager.renderTemplate( "rentalRequestConfirmationToRenter", variables ); return await this.emailClient.sendEmail( renter.email, `Rental Request Submitted - ${rental.item?.name || "Item"}`, htmlContent ); } catch (error) { console.error("Failed to send rental request confirmation email:", error); return { success: false, error: error.message }; } } /** * Send rental approval confirmation email to owner * @param {Object} owner - Owner user object * @param {string} owner.email - Owner's email address * @param {string} owner.firstName - Owner's first name * @param {string} owner.stripeConnectedAccountId - Owner's Stripe account ID * @param {Object} renter - Renter user object * @param {string} renter.firstName - Renter's first name * @param {string} renter.lastName - Renter's last name * @param {Object} rental - Rental object with all details * @param {number} rental.id - Rental ID * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.deliveryMethod - Delivery method * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.payoutAmount - Owner's payout amount * @param {string} rental.platformFee - Platform fee amount * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendRentalApprovalConfirmationEmail(owner, renter, rental) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; // Determine if Stripe setup is needed const hasStripeAccount = !!owner.stripeConnectedAccountId; const totalAmount = parseFloat(rental.totalAmount) || 0; const payoutAmount = parseFloat(rental.payoutAmount) || 0; const platformFee = parseFloat(rental.platformFee) || 0; // Build payment message const isPaidRental = totalAmount > 0; let paymentMessage = ""; if (isPaidRental) { paymentMessage = "their payment has been processed successfully."; } else { paymentMessage = "this is a free rental (no payment required)."; } // Build earnings section (only for paid rentals) let earningsSection = ""; if (isPaidRental) { earningsSection = `

Your Earnings

Total Rental Amount $${totalAmount.toFixed(2)}
Community Upkeep Fee (10%) -$${platformFee.toFixed(2)}
Your Payout $${payoutAmount.toFixed(2)}
`; } // Build conditional Stripe section based on Stripe status let stripeSection = ""; if (!hasStripeAccount && isPaidRental) { // Only show Stripe setup reminder for paid rentals stripeSection = `

⚠️ Action Required: Set Up Your Earnings Account

To receive your payout of $${payoutAmount.toFixed( 2 )} when this rental completes, you need to set up your earnings account.

Set Up Earnings to Get Paid

Why set up now?

Setup only takes about 5 minutes and you only need to do it once.

Set Up Earnings Account Now

Important: Without earnings setup, you won't receive payouts automatically when rentals complete.

`; } else if (hasStripeAccount && isPaidRental) { stripeSection = `

✓ Earnings Account Active

Your earnings account is set up. You'll automatically receive $${payoutAmount.toFixed( 2 )} when this rental completes.

View your earnings dashboard →

`; } // Format delivery method for display const deliveryMethodDisplay = rental.deliveryMethod === "delivery" ? "Delivery" : "Pickup"; const variables = { ownerName: owner.firstName || "there", itemName: rental.item?.name || "your item", renterName: `${renter.firstName} ${renter.lastName}`.trim() || "The renter", startDate: rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", endDate: rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", deliveryMethod: deliveryMethodDisplay, paymentMessage: paymentMessage, earningsSection: earningsSection, stripeSection: stripeSection, rentalDetailsUrl: `${frontendUrl}/owning?rentalId=${rental.id}`, }; const htmlContent = await this.templateManager.renderTemplate( "rentalApprovalConfirmationToOwner", variables ); const subject = `Rental Approved - ${rental.item?.name || "Your Item"}`; return await this.emailClient.sendEmail( owner.email, subject, htmlContent ); } catch (error) { console.error( "Failed to send rental approval confirmation email:", error ); return { success: false, error: error.message }; } } /** * Send rental declined email to renter * @param {Object} renter - Renter user object * @param {string} renter.email - Renter's email address * @param {string} renter.firstName - Renter's first name * @param {Object} rental - Rental object with all details * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.payoutAmount - Owner's payout amount * @param {string} rental.deliveryMethod - Delivery method * @param {string|null} declineReason - Reason for declining rental (optional) * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendRentalDeclinedEmail(renter, rental, declineReason) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const browseItemsUrl = `${frontendUrl}/`; // Determine payment message based on rental amount const totalAmount = parseFloat(rental.totalAmount) || 0; const paymentMessage = totalAmount > 0 ? "Since your request was declined before payment was processed, you will not be charged." : "No payment was required for this rental request."; // Build owner message section if decline reason provided const ownerMessage = declineReason ? `

Message from the owner:

${declineReason}

` : ""; const variables = { renterName: renter.firstName || "there", itemName: rental.item?.name || "the item", startDate: rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", endDate: rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", deliveryMethod: rental.deliveryMethod || "Not specified", paymentMessage: paymentMessage, ownerMessage: ownerMessage, browseItemsUrl: browseItemsUrl, payoutAmount: rental.payoutAmount ? parseFloat(rental.payoutAmount).toFixed(2) : "0.00", totalAmount: totalAmount.toFixed(2), }; const htmlContent = await this.templateManager.renderTemplate( "rentalDeclinedToRenter", variables ); return await this.emailClient.sendEmail( renter.email, `Rental Request Declined - ${rental.item?.name || "Item"}`, htmlContent ); } catch (error) { console.error("Failed to send rental declined email:", error); return { success: false, error: error.message }; } } /** * Send rental confirmation email with payment receipt (if applicable) * @param {string} userEmail - User's email address * @param {Object} notification - Notification object * @param {string} notification.title - Notification title * @param {string} notification.message - Notification message * @param {Object} rental - Rental object with all rental details * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.paymentStatus - Payment status * @param {string} rental.paymentMethodBrand - Payment method brand * @param {string} rental.paymentMethodLast4 - Last 4 digits of payment method * @param {string} rental.stripePaymentIntentId - Stripe payment intent ID * @param {string} rental.chargedAt - Payment charge timestamp * @param {string|null} recipientName - Recipient's name * @param {boolean} isRenter - Whether recipient is the renter (to show payment receipt) * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendRentalConfirmation( userEmail, notification, rental, recipientName = null, isRenter = false ) { if (!this.initialized) { await this.initialize(); } try { const itemName = rental?.item?.name || "Unknown Item"; const variables = { recipientName: recipientName || "there", title: notification.title, message: notification.message, itemName: itemName, startDate: rental?.startDateTime ? new Date(rental.startDateTime).toLocaleDateString() : "Not specified", endDate: rental?.endDateTime ? new Date(rental.endDateTime).toLocaleDateString() : "Not specified", isRenter: isRenter, }; // Add payment information if this is for the renter and rental has payment info let paymentSection = ""; if (isRenter) { const totalAmount = parseFloat(rental.totalAmount) || 0; const isPaidRental = totalAmount > 0 && rental.paymentStatus === "paid"; if (isPaidRental) { // Format payment method display let paymentMethodDisplay = "Payment method on file"; if (rental.paymentMethodBrand && rental.paymentMethodLast4) { const brandCapitalized = rental.paymentMethodBrand.charAt(0).toUpperCase() + rental.paymentMethodBrand.slice(1); paymentMethodDisplay = `${brandCapitalized} ending in ${rental.paymentMethodLast4}`; } const chargedAtFormatted = rental.chargedAt ? new Date(rental.chargedAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : new Date().toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }); // Build payment receipt section HTML paymentSection = `

Payment Receipt

💳

Payment Successful

Your payment has been processed. This email serves as your receipt.

Amount Charged $${totalAmount.toFixed(2)}
Payment Method ${paymentMethodDisplay}
Transaction ID ${ rental.stripePaymentIntentId || "N/A" }
Transaction Date ${chargedAtFormatted}

Note: Keep this email for your records. You can use the transaction ID above if you need to contact support about this payment.

`; } else if (totalAmount === 0) { // Free rental message paymentSection = `

No Payment Required: This is a free rental.

`; } } variables.paymentSection = paymentSection; const htmlContent = await this.templateManager.renderTemplate( "rentalConfirmationToUser", variables ); // Use clear, transactional subject line with item name const subject = `Rental Confirmation - ${itemName}`; return await this.emailClient.sendEmail(userEmail, subject, htmlContent); } catch (error) { console.error("Failed to send rental confirmation:", error); return { success: false, error: error.message }; } } /** * Send rental confirmation emails to both owner and renter * @param {Object} owner - Owner user object * @param {string} owner.email - Owner's email address * @param {string} owner.firstName - Owner's first name * @param {Object} renter - Renter user object * @param {string} renter.email - Renter's email address * @param {string} renter.firstName - Renter's first name * @param {Object} rental - Rental object with all details * @param {number} rental.id - Rental ID * @param {number} rental.ownerId - Owner's user ID * @param {number} rental.renterId - Renter's user ID * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @returns {Promise<{ownerEmailSent: boolean, renterEmailSent: boolean}>} */ async sendRentalConfirmationEmails(owner, renter, rental) { if (!this.initialized) { await this.initialize(); } const results = { ownerEmailSent: false, renterEmailSent: false, }; try { // Create notification data for owner const ownerNotification = { type: "rental_confirmed", title: "Rental Confirmed", message: `Your "${rental.item.name}" has been confirmed for rental.`, rentalId: rental.id, userId: rental.ownerId, metadata: { rentalStart: rental.startDateTime }, }; // Create notification data for renter const renterNotification = { type: "rental_confirmed", title: "Rental Confirmed", message: `Your rental of "${rental.item.name}" has been confirmed.`, rentalId: rental.id, userId: rental.renterId, metadata: { rentalStart: rental.startDateTime }, }; // Send email to owner - independent error handling if (owner?.email) { try { const ownerResult = await this.sendRentalConfirmation( owner.email, ownerNotification, rental, owner.firstName, false // isRenter = false for owner ); if (ownerResult.success) { console.log( `Rental confirmation email sent to owner: ${owner.email}` ); results.ownerEmailSent = true; } else { console.error( `Failed to send rental confirmation email to owner (${owner.email}):`, ownerResult.error ); } } catch (error) { console.error( `Failed to send rental confirmation email to owner (${owner.email}):`, error.message ); } } // Send email to renter - independent error handling if (renter?.email) { try { const renterResult = await this.sendRentalConfirmation( renter.email, renterNotification, rental, renter.firstName, true // isRenter = true for renter (enables payment receipt) ); if (renterResult.success) { console.log( `Rental confirmation email sent to renter: ${renter.email}` ); results.renterEmailSent = true; } else { console.error( `Failed to send rental confirmation email to renter (${renter.email}):`, renterResult.error ); } } catch (error) { console.error( `Failed to send rental confirmation email to renter (${renter.email}):`, error.message ); } } } catch (error) { console.error( "Error fetching user data for rental confirmation emails:", error ); } return results; } /** * Send rental cancellation emails to both parties * @param {Object} owner - Owner user object * @param {string} owner.email - Owner's email address * @param {string} owner.firstName - Owner's first name * @param {Object} renter - Renter user object * @param {string} renter.email - Renter's email address * @param {string} renter.firstName - Renter's first name * @param {Object} rental - Rental object with all details * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.cancelledBy - Who cancelled ('owner' or 'renter') * @param {string} rental.cancelledAt - Cancellation timestamp * @param {string} rental.totalAmount - Total rental amount * @param {Object} refundInfo - Refund information * @param {number} refundInfo.amount - Refund amount * @param {number} refundInfo.percentage - Refund percentage (0-1) * @param {string} refundInfo.reason - Refund reason description * @returns {Promise<{confirmationEmailSent: boolean, notificationEmailSent: boolean}>} */ async sendRentalCancellationEmails(owner, renter, rental, refundInfo) { if (!this.initialized) { await this.initialize(); } const results = { confirmationEmailSent: false, notificationEmailSent: false, }; try { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const browseUrl = `${frontendUrl}/`; const cancelledBy = rental.cancelledBy; const itemName = rental.item?.name || "the item"; const startDate = rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified"; const endDate = rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified"; const cancelledAt = rental.cancelledAt ? new Date(rental.cancelledAt).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified"; // Determine who gets confirmation and who gets notification let confirmationRecipient, notificationRecipient; let confirmationRecipientName, notificationRecipientName; let cancellationMessage, additionalInfo; if (cancelledBy === "owner") { // Owner cancelled: owner gets confirmation, renter gets notification confirmationRecipient = owner.email; confirmationRecipientName = owner.firstName || "there"; notificationRecipient = renter.email; notificationRecipientName = renter.firstName || "there"; cancellationMessage = `The owner has cancelled the rental for ${itemName}. We apologize for any inconvenience this may cause.`; // Only show refund info if rental had a cost if (rental.totalAmount > 0) { additionalInfo = `

Full Refund Processed

You will receive a full refund of $${refundInfo.amount.toFixed( 2 )}. The refund will appear in your account within 5-10 business days.

Browse Other Items
`; } else { additionalInfo = `

This rental has been cancelled by the owner. We apologize for any inconvenience.

Browse Other Items
`; } } else { // Renter cancelled: renter gets confirmation, owner gets notification confirmationRecipient = renter.email; confirmationRecipientName = renter.firstName || "there"; notificationRecipient = owner.email; notificationRecipientName = owner.firstName || "there"; cancellationMessage = `The renter has cancelled their rental for ${itemName}.`; additionalInfo = `

Your item is now available

Your item is now available for other renters to book for these dates.

`; } // Build refund section for confirmation email (only for paid rentals) let refundSection = ""; if (rental.totalAmount > 0) { if (refundInfo.amount > 0) { const refundPercentage = (refundInfo.percentage * 100).toFixed(0); refundSection = `

Refund Information

$${refundInfo.amount.toFixed(2)}

Refund Amount: $${refundInfo.amount.toFixed( 2 )} (${refundPercentage}% of total)

Reason: ${refundInfo.reason}

Processing Time: Refunds typically appear within 5-10 business days.

`; } else { refundSection = `

Refund Information

No Refund Available

${refundInfo.reason}

`; } } // Send confirmation email to canceller try { const confirmationVariables = { recipientName: confirmationRecipientName, itemName: itemName, startDate: startDate, endDate: endDate, cancelledAt: cancelledAt, refundSection: refundSection, }; const confirmationHtml = await this.templateManager.renderTemplate( "rentalCancellationConfirmationToUser", confirmationVariables ); const confirmationResult = await this.emailClient.sendEmail( confirmationRecipient, `Cancellation Confirmed - ${itemName}`, confirmationHtml ); if (confirmationResult.success) { console.log( `Cancellation confirmation email sent to ${cancelledBy}: ${confirmationRecipient}` ); results.confirmationEmailSent = true; } } catch (error) { console.error( `Failed to send cancellation confirmation email to ${cancelledBy}:`, error.message ); } // Send notification email to other party try { const notificationVariables = { recipientName: notificationRecipientName, itemName: itemName, startDate: startDate, endDate: endDate, cancelledAt: cancelledAt, cancellationMessage: cancellationMessage, additionalInfo: additionalInfo, }; const notificationHtml = await this.templateManager.renderTemplate( "rentalCancellationNotificationToUser", notificationVariables ); const notificationResult = await this.emailClient.sendEmail( notificationRecipient, `Rental Cancelled - ${itemName}`, notificationHtml ); if (notificationResult.success) { console.log( `Cancellation notification email sent to ${ cancelledBy === "owner" ? "renter" : "owner" }: ${notificationRecipient}` ); results.notificationEmailSent = true; } } catch (error) { console.error( `Failed to send cancellation notification email:`, error.message ); } } catch (error) { console.error("Error sending cancellation emails:", error); } return results; } /** * Send rental completion emails to both owner and renter * @param {Object} owner - Owner user object * @param {string} owner.email - Owner's email address * @param {string} owner.firstName - Owner's first name * @param {string} owner.lastName - Owner's last name * @param {string} owner.stripeConnectedAccountId - Owner's Stripe account ID * @param {Object} renter - Renter user object * @param {string} renter.email - Renter's email address * @param {string} renter.firstName - Renter's first name * @param {string} renter.lastName - Renter's last name * @param {Object} rental - Rental object with all details * @param {number} rental.id - Rental ID * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.actualReturnDateTime - Actual return timestamp * @param {string} rental.itemReviewSubmittedAt - Review submission timestamp * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.payoutAmount - Owner's payout amount * @param {string} rental.platformFee - Platform fee amount * @returns {Promise<{renterEmailSent: boolean, ownerEmailSent: boolean}>} */ async sendRentalCompletionEmails(owner, renter, rental) { if (!this.initialized) { await this.initialize(); } const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const results = { renterEmailSent: false, ownerEmailSent: false, }; try { // Format dates const startDate = rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified"; const endDate = rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified"; const returnedDate = rental.actualReturnDateTime ? new Date(rental.actualReturnDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : endDate; // Check if renter has already submitted a review const hasReviewed = !!rental.itemReviewSubmittedAt; // Build review section for renter email let reviewSection = ""; if (!hasReviewed) { reviewSection = `

Share Your Experience

Help the community by leaving a review!

Your feedback helps other renters make informed decisions and supports quality listings on Village Share.

Leave a Review

`; } else { reviewSection = `

✓ Thank You for Your Review!

Your feedback has been submitted and helps strengthen the Village Share community.

`; } // Send email to renter try { const renterVariables = { renterName: renter.firstName || "there", itemName: rental.item?.name || "the item", ownerName: owner.firstName || "the owner", startDate: startDate, endDate: endDate, returnedDate: returnedDate, reviewSection: reviewSection, browseItemsUrl: `${frontendUrl}/`, }; const renterHtmlContent = await this.templateManager.renderTemplate( "rentalCompletionThankYouToRenter", renterVariables ); const renterResult = await this.emailClient.sendEmail( renter.email, `Thank You for Returning "${rental.item?.name || "Item"}" On Time!`, renterHtmlContent ); if (renterResult.success) { console.log( `Rental completion thank you email sent to renter: ${renter.email}` ); results.renterEmailSent = true; } else { console.error( `Failed to send rental completion email to renter (${renter.email}):`, renterResult.error ); } } catch (emailError) { logger.error("Failed to send rental completion email to renter", { error: emailError.message, stack: emailError.stack, renterEmail: renter.email, rentalId: rental.id, }); } // Prepare owner email const hasStripeAccount = !!owner.stripeConnectedAccountId; const totalAmount = parseFloat(rental.totalAmount) || 0; const payoutAmount = parseFloat(rental.payoutAmount) || 0; const platformFee = parseFloat(rental.platformFee) || 0; const isPaidRental = totalAmount > 0; // Build earnings section for owner (only for paid rentals) let earningsSection = ""; if (isPaidRental) { earningsSection = `

Your Earnings

Total Rental Amount $${totalAmount.toFixed(2)}
Community Upkeep Fee (10%) -$${platformFee.toFixed(2)}
Your Payout $${payoutAmount.toFixed(2)}

Your earnings are transferred immediately when the rental is marked complete. Funds typically reach your bank within 2-7 business days.

`; } // Build Stripe section for owner let stripeSection = ""; if (!hasStripeAccount && isPaidRental) { // Show Stripe setup reminder for paid rentals stripeSection = `

⚠️ Action Required: Set Up Your Earnings Account

To receive your payout of $${payoutAmount.toFixed( 2 )}, you need to set up your earnings account.

Set Up Earnings to Get Paid

Why set up now?

Setup only takes about 5 minutes and you only need to do it once.

Set Up Earnings Account Now

Important: Without earnings setup, you won't receive payouts automatically.

`; } else if (hasStripeAccount && isPaidRental) { stripeSection = `

✓ Payout Initiated

Your earnings of $${payoutAmount.toFixed( 2 )} have been transferred to your Stripe account.

Funds typically reach your bank within 2-7 business days.

View your earnings dashboard →

`; } // Send email to owner try { const ownerVariables = { ownerName: owner.firstName || "there", itemName: rental.item?.name || "your item", renterName: `${renter.firstName} ${renter.lastName}`.trim() || "The renter", startDate: startDate, endDate: endDate, returnedDate: returnedDate, earningsSection: earningsSection, stripeSection: stripeSection, owningUrl: `${frontendUrl}/owning`, }; const ownerHtmlContent = await this.templateManager.renderTemplate( "rentalCompletionCongratsToOwner", ownerVariables ); const ownerResult = await this.emailClient.sendEmail( owner.email, `Rental Complete - ${rental.item?.name || "Your Item"}`, ownerHtmlContent ); if (ownerResult.success) { console.log( `Rental completion congratulations email sent to owner: ${owner.email}` ); results.ownerEmailSent = true; } else { console.error( `Failed to send rental completion email to owner (${owner.email}):`, ownerResult.error ); } } catch (emailError) { logger.error("Failed to send rental completion email to owner", { error: emailError.message, stack: emailError.stack, ownerEmail: owner.email, rentalId: rental.id, }); } } catch (error) { logger.error("Error sending rental completion emails", { error: error.message, stack: error.stack, rentalId: rental?.id, }); } return results; } /** * Send payout received email to owner * @param {Object} owner - Owner user object * @param {string} owner.email - Owner's email address * @param {string} owner.firstName - Owner's first name * @param {Object} rental - Rental object with all details * @param {Object} rental.item - Item object * @param {string} rental.item.name - Item name * @param {string} rental.startDateTime - Rental start date * @param {string} rental.endDateTime - Rental end date * @param {string} rental.totalAmount - Total rental amount * @param {string} rental.platformFee - Platform fee amount * @param {string} rental.payoutAmount - Owner's payout amount * @param {string} rental.stripeTransferId - Stripe transfer ID * @returns {Promise<{success: boolean, messageId?: string, error?: string}>} */ async sendPayoutReceivedEmail(owner, rental) { if (!this.initialized) { await this.initialize(); } try { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const earningsDashboardUrl = `${frontendUrl}/earnings`; // Format currency values const totalAmount = parseFloat(rental.totalAmount) || 0; const platformFee = parseFloat(rental.platformFee) || 0; const payoutAmount = parseFloat(rental.payoutAmount) || 0; const variables = { ownerName: owner.firstName || "there", itemName: rental.item?.name || "your item", startDate: rental.startDateTime ? new Date(rental.startDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", endDate: rental.endDateTime ? new Date(rental.endDateTime).toLocaleString("en-US", { dateStyle: "medium", timeStyle: "short", }) : "Not specified", totalAmount: totalAmount.toFixed(2), platformFee: platformFee.toFixed(2), payoutAmount: payoutAmount.toFixed(2), stripeTransferId: rental.stripeTransferId || "N/A", earningsDashboardUrl: earningsDashboardUrl, }; const htmlContent = await this.templateManager.renderTemplate( "payoutReceivedToOwner", variables ); return await this.emailClient.sendEmail( owner.email, `Earnings Received - $${payoutAmount.toFixed(2)} for ${ rental.item?.name || "Your Item" }`, htmlContent ); } catch (error) { console.error("Failed to send payout received email:", error); return { success: false, error: error.message }; } } } module.exports = RentalFlowEmailService;