diff --git a/backend/models/Rental.js b/backend/models/Rental.js index 9008330..fc4bb9b 100644 --- a/backend/models/Rental.js +++ b/backend/models/Rental.js @@ -64,15 +64,15 @@ const Rental = sequelize.define("Rental", { "damaged", "lost" ), - defaultValue: "pending", + allowNull: false, }, paymentStatus: { type: DataTypes.ENUM("pending", "paid", "refunded", "not_required"), - defaultValue: "pending", + allowNull: false, }, payoutStatus: { - type: DataTypes.ENUM("pending", "processing", "completed", "failed"), - defaultValue: "pending", + type: DataTypes.ENUM("pending", "completed", "failed"), + allowNull: true, }, payoutProcessedAt: { type: DataTypes.DATE, diff --git a/backend/routes/rentals.js b/backend/routes/rentals.js index eac230b..a25d3f2 100644 --- a/backend/routes/rentals.js +++ b/backend/routes/rentals.js @@ -823,7 +823,7 @@ router.post("/:id/mark-completed", authenticateToken, async (req, res) => { }); } - await rental.update({ status: "completed" }); + await rental.update({ status: "completed", payoutStatus: "pending" }); const updatedRental = await Rental.findByPk(rental.id, { include: [ @@ -1131,6 +1131,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { // Item returned on time updatedRental = await rental.update({ status: "completed", + payoutStatus: "pending", actualReturnDateTime: actualReturnDateTime || rental.endDateTime, notes: notes || null, }); @@ -1174,6 +1175,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { // Item returned damaged const damageUpdates = { status: "damaged", + payoutStatus: "pending", actualReturnDateTime: actualReturnDateTime || rental.endDateTime, notes: notes || null, }; @@ -1217,6 +1219,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res) => { // Item reported as lost updatedRental = await rental.update({ status: "lost", + payoutStatus: "pending", itemLostReportedAt: new Date(), notes: notes || null, }); diff --git a/backend/services/lateReturnService.js b/backend/services/lateReturnService.js index d9a63e4..078305a 100644 --- a/backend/services/lateReturnService.js +++ b/backend/services/lateReturnService.js @@ -81,6 +81,7 @@ class LateReturnService { const updates = { actualReturnDateTime: new Date(actualReturnDateTime), status: lateCalculation.isLate ? "returned_late" : "completed", + payoutStatus: "pending", }; if (notes) { diff --git a/backend/services/payoutService.js b/backend/services/payoutService.js index 64e49a3..6a703ed 100644 --- a/backend/services/payoutService.js +++ b/backend/services/payoutService.js @@ -50,11 +50,6 @@ class PayoutService { throw new Error("Invalid payout amount"); } - // Update status to processing - await rental.update({ - payoutStatus: "processing", - }); - // Create Stripe transfer const transfer = await StripeService.createTransfer({ amount: rental.payoutAmount, diff --git a/backend/tests/unit/services/payoutService.test.js b/backend/tests/unit/services/payoutService.test.js index f8a2fc9..934ecb6 100644 --- a/backend/tests/unit/services/payoutService.test.js +++ b/backend/tests/unit/services/payoutService.test.js @@ -186,11 +186,6 @@ describe('PayoutService', () => { it('should successfully process a rental payout', async () => { const result = await PayoutService.processRentalPayout(mockRental); - // Verify status update to processing - expect(mockRental.update).toHaveBeenNthCalledWith(1, { - payoutStatus: 'processing' - }); - // Verify Stripe transfer creation expect(mockCreateTransfer).toHaveBeenCalledWith({ amount: 9500, @@ -206,7 +201,7 @@ describe('PayoutService', () => { }); // Verify status update to completed - expect(mockRental.update).toHaveBeenNthCalledWith(2, { + expect(mockRental.update).toHaveBeenCalledWith({ payoutStatus: 'completed', payoutProcessedAt: expect.any(Date), stripeTransferId: 'tr_123456789' @@ -260,13 +255,8 @@ describe('PayoutService', () => { await expect(PayoutService.processRentalPayout(mockRental)) .rejects.toThrow('Stripe transfer failed'); - // Verify processing status was set - expect(mockRental.update).toHaveBeenNthCalledWith(1, { - payoutStatus: 'processing' - }); - // Verify failure status was set - expect(mockRental.update).toHaveBeenNthCalledWith(2, { + expect(mockRental.update).toHaveBeenCalledWith({ payoutStatus: 'failed' }); @@ -296,9 +286,7 @@ describe('PayoutService', () => { }); const dbError = new Error('Database completion update failed'); - mockRental.update - .mockResolvedValueOnce(true) // processing update succeeds - .mockRejectedValueOnce(dbError); // completion update fails + mockRental.update.mockRejectedValueOnce(dbError); await expect(PayoutService.processRentalPayout(mockRental)) .rejects.toThrow('Database completion update failed'); @@ -315,16 +303,14 @@ describe('PayoutService', () => { const updateError = new Error('Update failed status failed'); mockCreateTransfer.mockRejectedValue(stripeError); - mockRental.update - .mockResolvedValueOnce(true) // processing update succeeds - .mockRejectedValueOnce(updateError); // failed status update fails + mockRental.update.mockRejectedValueOnce(updateError); // The service will throw the update error since it happens in the catch block await expect(PayoutService.processRentalPayout(mockRental)) .rejects.toThrow('Update failed status failed'); // Should still attempt to update to failed status - expect(mockRental.update).toHaveBeenNthCalledWith(2, { + expect(mockRental.update).toHaveBeenCalledWith({ payoutStatus: 'failed' }); }); diff --git a/frontend/src/pages/EarningsDashboard.tsx b/frontend/src/pages/EarningsDashboard.tsx index a961a97..9ea612b 100644 --- a/frontend/src/pages/EarningsDashboard.tsx +++ b/frontend/src/pages/EarningsDashboard.tsx @@ -211,15 +211,15 @@ const EarningsDashboard: React.FC = () => { className={`badge ${ rental.payoutStatus === "completed" ? "bg-success" - : rental.payoutStatus === "processing" - ? "bg-warning" + : rental.payoutStatus === "failed" + ? "bg-danger" : "bg-secondary" }`} > {rental.payoutStatus === "completed" ? "Paid" - : rental.payoutStatus === "processing" - ? "Processing" + : rental.payoutStatus === "failed" + ? "Failed" : "Pending"} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 7c2cc20..63fe50c 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -147,7 +147,7 @@ export interface Rental { stripePaymentIntentId?: string; stripePaymentMethodId?: string; // Payout status tracking - payoutStatus?: "pending" | "processing" | "completed" | "failed"; + payoutStatus?: "pending" | "completed" | "failed" | null; payoutProcessedAt?: string; stripeTransferId?: string; deliveryMethod: "pickup" | "delivery";