can cancel a rental request before owner approval
This commit is contained in:
@@ -93,8 +93,12 @@ class RefundService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check payment status - allow cancellation for both paid and free rentals
|
// Allow cancellation for pending rentals (before owner approval) or paid/free rentals
|
||||||
if (rental.paymentStatus !== "paid" && rental.paymentStatus !== "not_required") {
|
const isPendingRequest = rental.status === "pending";
|
||||||
|
const isPaymentSettled =
|
||||||
|
rental.paymentStatus === "paid" || rental.paymentStatus === "not_required";
|
||||||
|
|
||||||
|
if (!isPendingRequest && !isPaymentSettled) {
|
||||||
return {
|
return {
|
||||||
canCancel: false,
|
canCancel: false,
|
||||||
reason: "Cannot cancel rental that hasn't been paid",
|
reason: "Cannot cancel rental that hasn't been paid",
|
||||||
|
|||||||
@@ -277,8 +277,9 @@ describe('RefundService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Payment status validation', () => {
|
describe('Payment status validation', () => {
|
||||||
it('should reject cancellation for unpaid rental', () => {
|
it('should reject cancellation for confirmed rental with unpaid status', () => {
|
||||||
const rental = { ...baseRental, paymentStatus: 'pending' };
|
// Confirmed rentals require payment to be settled
|
||||||
|
const rental = { ...baseRental, status: 'confirmed', paymentStatus: 'pending' };
|
||||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@@ -288,8 +289,8 @@ describe('RefundService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject cancellation for failed payment', () => {
|
it('should reject cancellation for confirmed rental with failed payment', () => {
|
||||||
const rental = { ...baseRental, paymentStatus: 'failed' };
|
const rental = { ...baseRental, status: 'confirmed', paymentStatus: 'failed' };
|
||||||
const result = RefundService.validateCancellationEligibility(rental, 100);
|
const result = RefundService.validateCancellationEligibility(rental, 100);
|
||||||
|
|
||||||
expect(result).toEqual({
|
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', () => {
|
describe('Edge cases', () => {
|
||||||
it('should handle string user IDs that don\'t match', () => {
|
it('should handle string user IDs that don\'t match', () => {
|
||||||
const result = RefundService.validateCancellationEligibility(baseRental, '100');
|
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', () => {
|
describe('Error handling', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
|
|||||||
Reference in New Issue
Block a user