Compare commits
3 Commits
sprint4Sta
...
acd31725ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd31725ed | ||
|
|
0d96b1e67e | ||
|
|
f98354a1c8 |
@@ -1779,6 +1779,7 @@ model ItineraryHeader {
|
||||
toDate DateTime @map("to_date")
|
||||
toTime String @map("to_time") @db.VarChar(30)
|
||||
itineraryStatus String @default("draft") @map("itinerary_status") @db.VarChar(30)
|
||||
cancellationReason String? @map("cancellation_reason")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@ -498,6 +498,21 @@ getAllUserSavedItineraries:
|
||||
path: /itinerary/get-all-user-saved-itineraries
|
||||
method: get
|
||||
|
||||
cancelUserItinerary:
|
||||
handler: src/modules/user/handlers/itinerary/cancelUserItinerary.handler
|
||||
memorySize: 512
|
||||
package:
|
||||
patterns:
|
||||
- 'src/modules/user/**'
|
||||
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /itinerary/cancel-itinerary
|
||||
method: post
|
||||
|
||||
createRazorpayOrder:
|
||||
handler: src/modules/user/handlers/payment/createOrder.handler
|
||||
memorySize: 512
|
||||
|
||||
71
src/modules/user/handlers/itinerary/cancelUserItinerary.ts
Normal file
71
src/modules/user/handlers/itinerary/cancelUserItinerary.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||
import { ItineraryService } from '../../services/itinerary.service';
|
||||
|
||||
const itineraryService = new ItineraryService(prismaClient);
|
||||
|
||||
export const handler = safeHandler(async (
|
||||
event: APIGatewayProxyEvent,
|
||||
context?: Context,
|
||||
): Promise<APIGatewayProxyResult> => {
|
||||
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||
if (!token) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'This is a protected route. Please provide a valid token.',
|
||||
);
|
||||
}
|
||||
|
||||
const userInfo = await verifyUserToken(token);
|
||||
const userId = Number(userInfo.id);
|
||||
|
||||
if (!userId || Number.isNaN(userId)) {
|
||||
throw new ApiError(400, 'Invalid user ID');
|
||||
}
|
||||
|
||||
let body: Record<string, any> = {};
|
||||
if (event.body) {
|
||||
try {
|
||||
body = JSON.parse(event.body);
|
||||
} catch {
|
||||
throw new ApiError(400, 'Invalid JSON body');
|
||||
}
|
||||
}
|
||||
|
||||
const itineraryHeaderXid =
|
||||
body.itineraryHeaderXid !== undefined && body.itineraryHeaderXid !== null
|
||||
? Number(body.itineraryHeaderXid)
|
||||
: NaN;
|
||||
const reason =
|
||||
typeof body.reason === 'string' ? body.reason.trim() : '';
|
||||
|
||||
if (!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0) {
|
||||
throw new ApiError(400, 'Invalid itineraryHeaderXid.');
|
||||
}
|
||||
|
||||
if (!reason) {
|
||||
throw new ApiError(400, 'Cancellation reason is required.');
|
||||
}
|
||||
|
||||
const result = await itineraryService.cancelUserItinerary(
|
||||
userId,
|
||||
itineraryHeaderXid,
|
||||
reason,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Itinerary cancelled successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -2462,6 +2462,207 @@ export class ItineraryService {
|
||||
};
|
||||
}
|
||||
|
||||
async cancelUserItinerary(
|
||||
userXid: number,
|
||||
itineraryHeaderXid: number,
|
||||
cancellationReason: string,
|
||||
) {
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const itinerary = await tx.itineraryHeader.findFirst({
|
||||
where: {
|
||||
id: itineraryHeaderXid,
|
||||
ownerXid: userXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
itineraryNo: true,
|
||||
title: true,
|
||||
itineraryStatus: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!itinerary) {
|
||||
throw new ApiError(
|
||||
404,
|
||||
'Active itinerary not found for the logged-in user.',
|
||||
);
|
||||
}
|
||||
|
||||
const itineraryActivityIds = (
|
||||
await tx.itineraryActivities.findMany({
|
||||
where: {
|
||||
itineraryHeaderXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
})
|
||||
).map((item) => item.id);
|
||||
|
||||
const itineraryDetailIds = itineraryActivityIds.length
|
||||
? (
|
||||
await tx.itineraryDetails.findMany({
|
||||
where: {
|
||||
itineraryActivityXid: {
|
||||
in: itineraryActivityIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
})
|
||||
).map((item) => item.id)
|
||||
: [];
|
||||
|
||||
const itinerarySelectionIds = itineraryActivityIds.length
|
||||
? (
|
||||
await tx.itineraryActivitySelection.findMany({
|
||||
where: {
|
||||
itineraryActivityXid: {
|
||||
in: itineraryActivityIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
})
|
||||
).map((item) => item.id)
|
||||
: [];
|
||||
|
||||
if (itineraryDetailIds.length) {
|
||||
await tx.itineraryDetailTaxes.updateMany({
|
||||
where: {
|
||||
itineraryDetailXid: {
|
||||
in: itineraryDetailIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.itineraryDetails.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: itineraryDetailIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
itineraryStatus: 'cancelled',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (itinerarySelectionIds.length) {
|
||||
await tx.itineraryActivitySelectionFoodType.updateMany({
|
||||
where: {
|
||||
itineraryActivitySelectionXid: {
|
||||
in: itinerarySelectionIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.itineraryActivitySelectionEquipment.updateMany({
|
||||
where: {
|
||||
itineraryActivitySelectionXid: {
|
||||
in: itinerarySelectionIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.itineraryActivitySelection.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: itinerarySelectionIds,
|
||||
},
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
await tx.itineraryActivities.updateMany({
|
||||
where: {
|
||||
itineraryHeaderXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
bookingStatus: 'cancelled',
|
||||
},
|
||||
});
|
||||
|
||||
await tx.itineraryStartStopDetails.updateMany({
|
||||
where: {
|
||||
itineraryHeaderXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.itineraryMembers.updateMany({
|
||||
where: {
|
||||
itineraryHeaderXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
data: {
|
||||
isActive: false,
|
||||
memberStatus: 'cancelled',
|
||||
},
|
||||
});
|
||||
|
||||
await tx.$executeRaw`
|
||||
UPDATE "itn"."itinerary_header"
|
||||
SET
|
||||
"is_active" = false,
|
||||
"itinerary_status" = 'cancelled',
|
||||
"cancellation_reason" = ${cancellationReason},
|
||||
"updated_at" = NOW()
|
||||
WHERE "id" = ${itineraryHeaderXid}
|
||||
`;
|
||||
|
||||
return {
|
||||
itineraryHeaderXid: itinerary.id,
|
||||
itineraryNo: itinerary.itineraryNo,
|
||||
title: itinerary.title,
|
||||
itineraryStatus: 'cancelled',
|
||||
cancellationReason,
|
||||
isActive: false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
async bookItineraryAfterPayment(
|
||||
tx: Prisma.TransactionClient,
|
||||
userXid: number,
|
||||
|
||||
@@ -12,6 +12,17 @@ const razorpay = new Razorpay({
|
||||
key_secret: config.RAZORPAY_KEY_SECRET,
|
||||
});
|
||||
|
||||
const buildUniqueReceipt = (input?: string) => {
|
||||
const normalizedPrefix = (input?.trim() || 'receipt')
|
||||
.replace(/[^a-zA-Z0-9_-]/g, '_')
|
||||
.replace(/_+/g, '_')
|
||||
.slice(0, 20);
|
||||
const timePart = Date.now().toString(36);
|
||||
const randomPart = crypto.randomBytes(4).toString('hex');
|
||||
|
||||
return `${normalizedPrefix}_${timePart}_${randomPart}`.slice(0, 100);
|
||||
};
|
||||
|
||||
type RazorpayWebhookPayload = {
|
||||
event?: string;
|
||||
payload?: {
|
||||
@@ -91,7 +102,7 @@ export class PaymentService {
|
||||
}
|
||||
|
||||
const amountInPaise = Math.round(payload.amount * 100);
|
||||
const receipt = payload.receipt ?? `receipt_${Date.now()}`;
|
||||
const receipt = buildUniqueReceipt(payload.receipt);
|
||||
const currency = payload.currency ?? 'INR';
|
||||
|
||||
const order = (await razorpay.orders.create({
|
||||
|
||||
Reference in New Issue
Block a user