Compare commits
3 Commits
sprint4Sta
...
mayankSpri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd31725ed | ||
|
|
0d96b1e67e | ||
|
|
f98354a1c8 |
@@ -1779,6 +1779,7 @@ model ItineraryHeader {
|
|||||||
toDate DateTime @map("to_date")
|
toDate DateTime @map("to_date")
|
||||||
toTime String @map("to_time") @db.VarChar(30)
|
toTime String @map("to_time") @db.VarChar(30)
|
||||||
itineraryStatus String @default("draft") @map("itinerary_status") @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")
|
isActive Boolean @default(true) @map("is_active")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|||||||
@@ -498,6 +498,21 @@ getAllUserSavedItineraries:
|
|||||||
path: /itinerary/get-all-user-saved-itineraries
|
path: /itinerary/get-all-user-saved-itineraries
|
||||||
method: get
|
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:
|
createRazorpayOrder:
|
||||||
handler: src/modules/user/handlers/payment/createOrder.handler
|
handler: src/modules/user/handlers/payment/createOrder.handler
|
||||||
memorySize: 512
|
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(
|
async bookItineraryAfterPayment(
|
||||||
tx: Prisma.TransactionClient,
|
tx: Prisma.TransactionClient,
|
||||||
userXid: number,
|
userXid: number,
|
||||||
|
|||||||
@@ -12,6 +12,17 @@ const razorpay = new Razorpay({
|
|||||||
key_secret: config.RAZORPAY_KEY_SECRET,
|
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 = {
|
type RazorpayWebhookPayload = {
|
||||||
event?: string;
|
event?: string;
|
||||||
payload?: {
|
payload?: {
|
||||||
@@ -91,7 +102,7 @@ export class PaymentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const amountInPaise = Math.round(payload.amount * 100);
|
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 currency = payload.currency ?? 'INR';
|
||||||
|
|
||||||
const order = (await razorpay.orders.create({
|
const order = (await razorpay.orders.create({
|
||||||
|
|||||||
Reference in New Issue
Block a user