can cancel a rental request before owner approval

This commit is contained in:
jackiettran
2026-01-10 19:22:15 -05:00
parent 860b6d6160
commit 86cb8b3fe0
2 changed files with 70 additions and 6 deletions

View File

@@ -93,8 +93,12 @@ class RefundService {
};
}
// Check payment status - allow cancellation for both paid and free rentals
if (rental.paymentStatus !== "paid" && rental.paymentStatus !== "not_required") {
// Allow cancellation for pending rentals (before owner approval) or paid/free rentals
const isPendingRequest = rental.status === "pending";
const isPaymentSettled =
rental.paymentStatus === "paid" || rental.paymentStatus === "not_required";
if (!isPendingRequest && !isPaymentSettled) {
return {
canCancel: false,
reason: "Cannot cancel rental that hasn't been paid",

View File

@@ -277,8 +277,9 @@ describe('RefundService', () => {
});
describe('Payment status validation', () => {
it('should reject cancellation for unpaid rental', () => {
const rental = { ...baseRental, paymentStatus: 'pending' };
it('should reject cancellation for confirmed rental with unpaid status', () => {
// Confirmed rentals require payment to be settled
const rental = { ...baseRental, status: 'confirmed', paymentStatus: 'pending' };
const result = RefundService.validateCancellationEligibility(rental, 100);
expect(result).toEqual({
@@ -288,8 +289,8 @@ describe('RefundService', () => {
});
});
it('should reject cancellation for failed payment', () => {
const rental = { ...baseRental, paymentStatus: 'failed' };
it('should reject cancellation for confirmed rental with failed payment', () => {
const rental = { ...baseRental, status: 'confirmed', paymentStatus: 'failed' };
const result = RefundService.validateCancellationEligibility(rental, 100);
expect(result).toEqual({
@@ -311,6 +312,31 @@ describe('RefundService', () => {
});
});
describe('Pending rental cancellation (before owner approval)', () => {
it('should allow renter to cancel pending rental even with pending payment', () => {
// Pending rentals can be cancelled before owner approval, no payment processed yet
const rental = { ...baseRental, status: 'pending', paymentStatus: 'pending' };
const result = RefundService.validateCancellationEligibility(rental, 100);
expect(result).toEqual({
canCancel: true,
reason: 'Cancellation allowed',
cancelledBy: 'renter'
});
});
it('should allow owner to cancel pending rental', () => {
const rental = { ...baseRental, status: 'pending', paymentStatus: 'pending' };
const result = RefundService.validateCancellationEligibility(rental, 200);
expect(result).toEqual({
canCancel: true,
reason: 'Cancellation allowed',
cancelledBy: 'owner'
});
});
});
describe('Edge cases', () => {
it('should handle string user IDs that don\'t match', () => {
const result = RefundService.validateCancellationEligibility(baseRental, '100');
@@ -528,6 +554,40 @@ describe('RefundService', () => {
});
});
describe('Pending rental cancellation (before owner approval)', () => {
it('should process cancellation for pending rental without Stripe refund', async () => {
// Pending rental with no payment processed yet
mockRental.status = 'pending';
mockRental.paymentStatus = 'pending';
mockRental.stripePaymentIntentId = null;
jest.useFakeTimers();
jest.setSystemTime(new Date('2023-11-28T10:00:00Z'));
const result = await RefundService.processCancellation(1, 100, 'Changed my mind');
// No Stripe refund should be attempted
expect(mockCreateRefund).not.toHaveBeenCalled();
// Rental should be updated
expect(mockRentalUpdate).toHaveBeenCalledWith({
status: 'cancelled',
cancelledBy: 'renter',
cancelledAt: expect.any(Date),
refundAmount: 100.00,
refundProcessedAt: null,
refundReason: 'Changed my mind',
stripeRefundId: null,
payoutStatus: 'pending'
});
expect(result.refund.processed).toBe(false);
expect(result.refund.stripeRefundId).toBeNull();
jest.useRealTimers();
});
});
describe('Error handling', () => {
beforeEach(() => {
jest.useFakeTimers();