@mac777/project-pinecone-models 1.1.9 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/AuditLog.js CHANGED
@@ -2,13 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const mongoose_1 = require("mongoose");
4
4
  const auditLogSchema = new mongoose_1.Schema({
5
- userId: { type: String, required: true },
6
- action: { type: String, required: true },
7
- details: { type: Object, required: true },
8
- performedBy: { type: String, required: true },
9
- targetType: { type: String, required: true },
10
- targetId: { type: String, required: true },
11
- metadata: { type: Object, required: true },
5
+ userId: { type: String },
6
+ action: { type: String },
7
+ details: { type: Object },
8
+ performedBy: { type: String },
9
+ targetType: { type: String },
10
+ targetId: { type: String },
11
+ metadata: { type: Object },
12
12
  timestamp: { type: Date, default: Date.now },
13
13
  });
14
14
  exports.default = auditLogSchema;
@@ -0,0 +1,350 @@
1
+ import mongoose from "mongoose";
2
+ declare const _default: mongoose.Model<{
3
+ userId: mongoose.Types.ObjectId;
4
+ status: "pending" | "cancelled" | "confirmed" | "refunded";
5
+ pricing: {
6
+ currency: string;
7
+ platformFee: number;
8
+ subtotal: number;
9
+ paymentFee: number;
10
+ total: number;
11
+ hostPayout: number;
12
+ };
13
+ tickets: mongoose.Types.DocumentArray<{
14
+ quantity: number;
15
+ ticketVariantId: mongoose.Types.ObjectId;
16
+ variantName: string;
17
+ pricePerTicket: number;
18
+ subtotal: number;
19
+ }, mongoose.Types.Subdocument<mongoose.mongo.BSON.ObjectId, any, {
20
+ quantity: number;
21
+ ticketVariantId: mongoose.Types.ObjectId;
22
+ variantName: string;
23
+ pricePerTicket: number;
24
+ subtotal: number;
25
+ }> & {
26
+ quantity: number;
27
+ ticketVariantId: mongoose.Types.ObjectId;
28
+ variantName: string;
29
+ pricePerTicket: number;
30
+ subtotal: number;
31
+ }>;
32
+ eventId: mongoose.Types.ObjectId;
33
+ expiresAt: NativeDate;
34
+ requiresManualReview: boolean;
35
+ paymentMethod: "card" | "bkash" | "bank_transfer" | "free";
36
+ paymentStatus: "pending" | "succeeded" | "failed";
37
+ ticketIds: mongoose.Types.ObjectId[];
38
+ buyerEmail: string;
39
+ ticketCount: number;
40
+ paidAt?: NativeDate | null | undefined;
41
+ refundedAt?: NativeDate | null | undefined;
42
+ confirmedAt?: NativeDate | null | undefined;
43
+ cancelledAt?: NativeDate | null | undefined;
44
+ reminderSentAt?: NativeDate | null | undefined;
45
+ buyerPhone?: string | null | undefined;
46
+ ipAddress?: string | null | undefined;
47
+ userAgent?: string | null | undefined;
48
+ refund?: {
49
+ reason: "event_cancelled" | "user_request" | "fraud";
50
+ amount: number;
51
+ refundedAt: NativeDate;
52
+ stripeRefundId?: string | null | undefined;
53
+ } | null | undefined;
54
+ orderNumber?: string | null | undefined;
55
+ paymentId?: string | null | undefined;
56
+ manualReviewReason?: string | null | undefined;
57
+ } & mongoose.DefaultTimestampProps, {}, {}, {}, mongoose.Document<unknown, {}, {
58
+ userId: mongoose.Types.ObjectId;
59
+ status: "pending" | "cancelled" | "confirmed" | "refunded";
60
+ pricing: {
61
+ currency: string;
62
+ platformFee: number;
63
+ subtotal: number;
64
+ paymentFee: number;
65
+ total: number;
66
+ hostPayout: number;
67
+ };
68
+ tickets: mongoose.Types.DocumentArray<{
69
+ quantity: number;
70
+ ticketVariantId: mongoose.Types.ObjectId;
71
+ variantName: string;
72
+ pricePerTicket: number;
73
+ subtotal: number;
74
+ }, mongoose.Types.Subdocument<mongoose.mongo.BSON.ObjectId, any, {
75
+ quantity: number;
76
+ ticketVariantId: mongoose.Types.ObjectId;
77
+ variantName: string;
78
+ pricePerTicket: number;
79
+ subtotal: number;
80
+ }> & {
81
+ quantity: number;
82
+ ticketVariantId: mongoose.Types.ObjectId;
83
+ variantName: string;
84
+ pricePerTicket: number;
85
+ subtotal: number;
86
+ }>;
87
+ eventId: mongoose.Types.ObjectId;
88
+ expiresAt: NativeDate;
89
+ requiresManualReview: boolean;
90
+ paymentMethod: "card" | "bkash" | "bank_transfer" | "free";
91
+ paymentStatus: "pending" | "succeeded" | "failed";
92
+ ticketIds: mongoose.Types.ObjectId[];
93
+ buyerEmail: string;
94
+ ticketCount: number;
95
+ paidAt?: NativeDate | null | undefined;
96
+ refundedAt?: NativeDate | null | undefined;
97
+ confirmedAt?: NativeDate | null | undefined;
98
+ cancelledAt?: NativeDate | null | undefined;
99
+ reminderSentAt?: NativeDate | null | undefined;
100
+ buyerPhone?: string | null | undefined;
101
+ ipAddress?: string | null | undefined;
102
+ userAgent?: string | null | undefined;
103
+ refund?: {
104
+ reason: "event_cancelled" | "user_request" | "fraud";
105
+ amount: number;
106
+ refundedAt: NativeDate;
107
+ stripeRefundId?: string | null | undefined;
108
+ } | null | undefined;
109
+ orderNumber?: string | null | undefined;
110
+ paymentId?: string | null | undefined;
111
+ manualReviewReason?: string | null | undefined;
112
+ } & mongoose.DefaultTimestampProps, {}, {
113
+ timestamps: true;
114
+ strict: true;
115
+ }> & {
116
+ userId: mongoose.Types.ObjectId;
117
+ status: "pending" | "cancelled" | "confirmed" | "refunded";
118
+ pricing: {
119
+ currency: string;
120
+ platformFee: number;
121
+ subtotal: number;
122
+ paymentFee: number;
123
+ total: number;
124
+ hostPayout: number;
125
+ };
126
+ tickets: mongoose.Types.DocumentArray<{
127
+ quantity: number;
128
+ ticketVariantId: mongoose.Types.ObjectId;
129
+ variantName: string;
130
+ pricePerTicket: number;
131
+ subtotal: number;
132
+ }, mongoose.Types.Subdocument<mongoose.mongo.BSON.ObjectId, any, {
133
+ quantity: number;
134
+ ticketVariantId: mongoose.Types.ObjectId;
135
+ variantName: string;
136
+ pricePerTicket: number;
137
+ subtotal: number;
138
+ }> & {
139
+ quantity: number;
140
+ ticketVariantId: mongoose.Types.ObjectId;
141
+ variantName: string;
142
+ pricePerTicket: number;
143
+ subtotal: number;
144
+ }>;
145
+ eventId: mongoose.Types.ObjectId;
146
+ expiresAt: NativeDate;
147
+ requiresManualReview: boolean;
148
+ paymentMethod: "card" | "bkash" | "bank_transfer" | "free";
149
+ paymentStatus: "pending" | "succeeded" | "failed";
150
+ ticketIds: mongoose.Types.ObjectId[];
151
+ buyerEmail: string;
152
+ ticketCount: number;
153
+ paidAt?: NativeDate | null | undefined;
154
+ refundedAt?: NativeDate | null | undefined;
155
+ confirmedAt?: NativeDate | null | undefined;
156
+ cancelledAt?: NativeDate | null | undefined;
157
+ reminderSentAt?: NativeDate | null | undefined;
158
+ buyerPhone?: string | null | undefined;
159
+ ipAddress?: string | null | undefined;
160
+ userAgent?: string | null | undefined;
161
+ refund?: {
162
+ reason: "event_cancelled" | "user_request" | "fraud";
163
+ amount: number;
164
+ refundedAt: NativeDate;
165
+ stripeRefundId?: string | null | undefined;
166
+ } | null | undefined;
167
+ orderNumber?: string | null | undefined;
168
+ paymentId?: string | null | undefined;
169
+ manualReviewReason?: string | null | undefined;
170
+ } & mongoose.DefaultTimestampProps & {
171
+ _id: mongoose.Types.ObjectId;
172
+ } & {
173
+ __v: number;
174
+ }, mongoose.Schema<any, mongoose.Model<any, any, any, any, any, any>, {}, {}, {}, {}, {
175
+ timestamps: true;
176
+ strict: true;
177
+ }, {
178
+ userId: mongoose.Types.ObjectId;
179
+ status: "pending" | "cancelled" | "confirmed" | "refunded";
180
+ pricing: {
181
+ currency: string;
182
+ platformFee: number;
183
+ subtotal: number;
184
+ paymentFee: number;
185
+ total: number;
186
+ hostPayout: number;
187
+ };
188
+ tickets: mongoose.Types.DocumentArray<{
189
+ quantity: number;
190
+ ticketVariantId: mongoose.Types.ObjectId;
191
+ variantName: string;
192
+ pricePerTicket: number;
193
+ subtotal: number;
194
+ }, mongoose.Types.Subdocument<mongoose.mongo.BSON.ObjectId, any, {
195
+ quantity: number;
196
+ ticketVariantId: mongoose.Types.ObjectId;
197
+ variantName: string;
198
+ pricePerTicket: number;
199
+ subtotal: number;
200
+ }> & {
201
+ quantity: number;
202
+ ticketVariantId: mongoose.Types.ObjectId;
203
+ variantName: string;
204
+ pricePerTicket: number;
205
+ subtotal: number;
206
+ }>;
207
+ eventId: mongoose.Types.ObjectId;
208
+ expiresAt: NativeDate;
209
+ requiresManualReview: boolean;
210
+ paymentMethod: "card" | "bkash" | "bank_transfer" | "free";
211
+ paymentStatus: "pending" | "succeeded" | "failed";
212
+ ticketIds: mongoose.Types.ObjectId[];
213
+ buyerEmail: string;
214
+ ticketCount: number;
215
+ paidAt?: NativeDate | null | undefined;
216
+ refundedAt?: NativeDate | null | undefined;
217
+ confirmedAt?: NativeDate | null | undefined;
218
+ cancelledAt?: NativeDate | null | undefined;
219
+ reminderSentAt?: NativeDate | null | undefined;
220
+ buyerPhone?: string | null | undefined;
221
+ ipAddress?: string | null | undefined;
222
+ userAgent?: string | null | undefined;
223
+ refund?: {
224
+ reason: "event_cancelled" | "user_request" | "fraud";
225
+ amount: number;
226
+ refundedAt: NativeDate;
227
+ stripeRefundId?: string | null | undefined;
228
+ } | null | undefined;
229
+ orderNumber?: string | null | undefined;
230
+ paymentId?: string | null | undefined;
231
+ manualReviewReason?: string | null | undefined;
232
+ } & mongoose.DefaultTimestampProps, mongoose.Document<unknown, {}, mongoose.FlatRecord<{
233
+ userId: mongoose.Types.ObjectId;
234
+ status: "pending" | "cancelled" | "confirmed" | "refunded";
235
+ pricing: {
236
+ currency: string;
237
+ platformFee: number;
238
+ subtotal: number;
239
+ paymentFee: number;
240
+ total: number;
241
+ hostPayout: number;
242
+ };
243
+ tickets: mongoose.Types.DocumentArray<{
244
+ quantity: number;
245
+ ticketVariantId: mongoose.Types.ObjectId;
246
+ variantName: string;
247
+ pricePerTicket: number;
248
+ subtotal: number;
249
+ }, mongoose.Types.Subdocument<mongoose.mongo.BSON.ObjectId, any, {
250
+ quantity: number;
251
+ ticketVariantId: mongoose.Types.ObjectId;
252
+ variantName: string;
253
+ pricePerTicket: number;
254
+ subtotal: number;
255
+ }> & {
256
+ quantity: number;
257
+ ticketVariantId: mongoose.Types.ObjectId;
258
+ variantName: string;
259
+ pricePerTicket: number;
260
+ subtotal: number;
261
+ }>;
262
+ eventId: mongoose.Types.ObjectId;
263
+ expiresAt: NativeDate;
264
+ requiresManualReview: boolean;
265
+ paymentMethod: "card" | "bkash" | "bank_transfer" | "free";
266
+ paymentStatus: "pending" | "succeeded" | "failed";
267
+ ticketIds: mongoose.Types.ObjectId[];
268
+ buyerEmail: string;
269
+ ticketCount: number;
270
+ paidAt?: NativeDate | null | undefined;
271
+ refundedAt?: NativeDate | null | undefined;
272
+ confirmedAt?: NativeDate | null | undefined;
273
+ cancelledAt?: NativeDate | null | undefined;
274
+ reminderSentAt?: NativeDate | null | undefined;
275
+ buyerPhone?: string | null | undefined;
276
+ ipAddress?: string | null | undefined;
277
+ userAgent?: string | null | undefined;
278
+ refund?: {
279
+ reason: "event_cancelled" | "user_request" | "fraud";
280
+ amount: number;
281
+ refundedAt: NativeDate;
282
+ stripeRefundId?: string | null | undefined;
283
+ } | null | undefined;
284
+ orderNumber?: string | null | undefined;
285
+ paymentId?: string | null | undefined;
286
+ manualReviewReason?: string | null | undefined;
287
+ } & mongoose.DefaultTimestampProps>, {}, mongoose.ResolveSchemaOptions<{
288
+ timestamps: true;
289
+ strict: true;
290
+ }>> & mongoose.FlatRecord<{
291
+ userId: mongoose.Types.ObjectId;
292
+ status: "pending" | "cancelled" | "confirmed" | "refunded";
293
+ pricing: {
294
+ currency: string;
295
+ platformFee: number;
296
+ subtotal: number;
297
+ paymentFee: number;
298
+ total: number;
299
+ hostPayout: number;
300
+ };
301
+ tickets: mongoose.Types.DocumentArray<{
302
+ quantity: number;
303
+ ticketVariantId: mongoose.Types.ObjectId;
304
+ variantName: string;
305
+ pricePerTicket: number;
306
+ subtotal: number;
307
+ }, mongoose.Types.Subdocument<mongoose.mongo.BSON.ObjectId, any, {
308
+ quantity: number;
309
+ ticketVariantId: mongoose.Types.ObjectId;
310
+ variantName: string;
311
+ pricePerTicket: number;
312
+ subtotal: number;
313
+ }> & {
314
+ quantity: number;
315
+ ticketVariantId: mongoose.Types.ObjectId;
316
+ variantName: string;
317
+ pricePerTicket: number;
318
+ subtotal: number;
319
+ }>;
320
+ eventId: mongoose.Types.ObjectId;
321
+ expiresAt: NativeDate;
322
+ requiresManualReview: boolean;
323
+ paymentMethod: "card" | "bkash" | "bank_transfer" | "free";
324
+ paymentStatus: "pending" | "succeeded" | "failed";
325
+ ticketIds: mongoose.Types.ObjectId[];
326
+ buyerEmail: string;
327
+ ticketCount: number;
328
+ paidAt?: NativeDate | null | undefined;
329
+ refundedAt?: NativeDate | null | undefined;
330
+ confirmedAt?: NativeDate | null | undefined;
331
+ cancelledAt?: NativeDate | null | undefined;
332
+ reminderSentAt?: NativeDate | null | undefined;
333
+ buyerPhone?: string | null | undefined;
334
+ ipAddress?: string | null | undefined;
335
+ userAgent?: string | null | undefined;
336
+ refund?: {
337
+ reason: "event_cancelled" | "user_request" | "fraud";
338
+ amount: number;
339
+ refundedAt: NativeDate;
340
+ stripeRefundId?: string | null | undefined;
341
+ } | null | undefined;
342
+ orderNumber?: string | null | undefined;
343
+ paymentId?: string | null | undefined;
344
+ manualReviewReason?: string | null | undefined;
345
+ } & mongoose.DefaultTimestampProps> & {
346
+ _id: mongoose.Types.ObjectId;
347
+ } & {
348
+ __v: number;
349
+ }>>;
350
+ export default _default;
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const mongoose_1 = __importDefault(require("mongoose"));
7
+ /* ----------------------------------
8
+ * Embedded Schemas
9
+ * ---------------------------------- */
10
+ const orderTicketSchema = new mongoose_1.default.Schema({
11
+ ticketVariantId: {
12
+ type: mongoose_1.default.Schema.Types.ObjectId,
13
+ required: true,
14
+ ref: "TicketVariant"
15
+ },
16
+ variantName: {
17
+ type: String,
18
+ required: true,
19
+ trim: true
20
+ },
21
+ quantity: {
22
+ type: Number,
23
+ required: true,
24
+ min: 1
25
+ },
26
+ pricePerTicket: {
27
+ type: Number,
28
+ required: true,
29
+ min: 0
30
+ },
31
+ subtotal: {
32
+ type: Number,
33
+ required: true,
34
+ min: 0
35
+ }
36
+ }, { _id: false });
37
+ const pricingSchema = new mongoose_1.default.Schema({
38
+ subtotal: { type: Number, required: true, min: 0 },
39
+ platformFee: { type: Number, required: true, min: 0 },
40
+ paymentFee: { type: Number, required: true, min: 0 },
41
+ total: { type: Number, required: true, min: 0 },
42
+ currency: { type: String, default: "BDT" },
43
+ hostPayout: { type: Number, required: true, min: 0 }
44
+ }, { _id: false });
45
+ const refundSchema = new mongoose_1.default.Schema({
46
+ reason: {
47
+ type: String,
48
+ enum: ["event_cancelled", "user_request", "fraud"],
49
+ required: true
50
+ },
51
+ amount: {
52
+ type: Number,
53
+ required: true,
54
+ min: 0
55
+ },
56
+ refundedAt: {
57
+ type: Date,
58
+ default: Date.now
59
+ },
60
+ stripeRefundId: String
61
+ }, { _id: false });
62
+ /* ----------------------------------
63
+ * Main Order Schema
64
+ * ---------------------------------- */
65
+ const orderSchema = new mongoose_1.default.Schema({
66
+ /* ---------- Identity ---------- */
67
+ orderNumber: {
68
+ type: String,
69
+ unique: true,
70
+ index: true
71
+ },
72
+ userId: {
73
+ type: mongoose_1.default.Schema.Types.ObjectId,
74
+ ref: "User",
75
+ required: true,
76
+ index: true
77
+ },
78
+ eventId: {
79
+ type: mongoose_1.default.Schema.Types.ObjectId,
80
+ ref: "Event",
81
+ required: true,
82
+ index: true
83
+ },
84
+ /* ---------- Tickets ---------- */
85
+ tickets: {
86
+ type: [orderTicketSchema],
87
+ validate: (v) => v.length > 0
88
+ },
89
+ ticketCount: {
90
+ type: Number,
91
+ required: true,
92
+ min: 1
93
+ },
94
+ ticketIds: [{
95
+ type: mongoose_1.default.Schema.Types.ObjectId,
96
+ ref: "Ticket"
97
+ }],
98
+ /* ---------- Pricing ---------- */
99
+ pricing: {
100
+ type: pricingSchema,
101
+ required: true
102
+ },
103
+ /* ---------- Payment ---------- */
104
+ paymentId: String,
105
+ paymentMethod: {
106
+ type: String,
107
+ enum: ["card", "bkash", "bank_transfer", "free"],
108
+ required: true
109
+ },
110
+ paymentStatus: {
111
+ type: String,
112
+ enum: ["pending", "succeeded", "failed"],
113
+ default: "pending",
114
+ index: true
115
+ },
116
+ paidAt: Date,
117
+ /* ---------- Order Lifecycle ---------- */
118
+ status: {
119
+ type: String,
120
+ enum: ["pending", "confirmed", "cancelled", "refunded"],
121
+ default: "pending",
122
+ index: true
123
+ },
124
+ requiresManualReview: {
125
+ type: Boolean,
126
+ default: false
127
+ },
128
+ manualReviewReason: String,
129
+ confirmedAt: Date,
130
+ cancelledAt: Date,
131
+ refundedAt: Date,
132
+ expiresAt: {
133
+ type: Date,
134
+ required: true,
135
+ index: true
136
+ },
137
+ reminderSentAt: Date,
138
+ /* ---------- Buyer ---------- */
139
+ buyerEmail: {
140
+ type: String,
141
+ required: true,
142
+ lowercase: true,
143
+ trim: true,
144
+ index: true
145
+ },
146
+ buyerPhone: String,
147
+ /* ---------- Refund ---------- */
148
+ refund: {
149
+ type: refundSchema,
150
+ validate: {
151
+ validator(refund) {
152
+ return !refund || refund.amount <= this.pricing.total;
153
+ },
154
+ message: "Refund amount exceeds order total"
155
+ }
156
+ },
157
+ /* ---------- Audit ---------- */
158
+ ipAddress: String,
159
+ userAgent: String
160
+ }, {
161
+ timestamps: true,
162
+ strict: true
163
+ });
164
+ /* ----------------------------------
165
+ * Indexes
166
+ * ---------------------------------- */
167
+ orderSchema.index({ status: 1, createdAt: -1 });
168
+ orderSchema.index({ eventId: 1, status: 1, createdAt: -1 });
169
+ orderSchema.index({ paymentStatus: 1, createdAt: -1 });
170
+ orderSchema.index({ expiresAt: 1 }, {
171
+ expireAfterSeconds: 0,
172
+ partialFilterExpression: { status: "pending" }
173
+ });
174
+ /* ----------------------------------
175
+ * Hooks
176
+ * ---------------------------------- */
177
+ // Generate order number
178
+ orderSchema.pre("save", function (next) {
179
+ if (this.isNew && !this.orderNumber) {
180
+ const ts = Date.now().toString().slice(-6);
181
+ const rand = Math.random().toString(36).substring(2, 8).toUpperCase();
182
+ this.orderNumber = `ORD-${ts}-${rand}`;
183
+ }
184
+ next();
185
+ });
186
+ // Compute ticket count
187
+ orderSchema.pre("validate", function (next) {
188
+ if (this.tickets?.length) {
189
+ this.ticketCount = this.tickets.reduce((sum, t) => sum + t.quantity, 0);
190
+ }
191
+ next();
192
+ });
193
+ // Pricing immutability after confirmation
194
+ orderSchema.pre("save", function (next) {
195
+ if (!this.isNew && this.isModified("pricing") && this.status !== "pending") {
196
+ return next(new Error("Pricing cannot be modified after confirmation"));
197
+ }
198
+ next();
199
+ });
200
+ /* ----------------------------------
201
+ * Export
202
+ * ---------------------------------- */
203
+ exports.default = mongoose_1.default.model("Order", orderSchema);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mac777/project-pinecone-models",
3
- "version": "1.1.9",
3
+ "version": "1.1.12",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
package/src/AuditLog.ts CHANGED
@@ -12,13 +12,13 @@ export interface IAuditLog extends Document {
12
12
  }
13
13
 
14
14
  const auditLogSchema = new Schema<IAuditLog>({
15
- userId: { type: String, required: true },
16
- action: { type: String, required: true },
17
- details: { type: Object, required: true },
18
- performedBy: { type: String, required: true },
19
- targetType: { type: String, required: true },
20
- targetId: { type: String, required: true },
21
- metadata: { type: Object, required: true },
15
+ userId: { type: String },
16
+ action: { type: String },
17
+ details: { type: Object },
18
+ performedBy: { type: String },
19
+ targetType: { type: String },
20
+ targetId: { type: String },
21
+ metadata: { type: Object },
22
22
  timestamp: { type: Date, default: Date.now },
23
23
  });
24
24
 
package/src/Order.ts CHANGED
@@ -198,4 +198,4 @@ orderSchema.pre('save', function(next) {
198
198
  next();
199
199
  });
200
200
 
201
- export default orderSchema;
201
+ export default orderSchema;
@@ -0,0 +1,253 @@
1
+ import mongoose from "mongoose";
2
+
3
+ /* ----------------------------------
4
+ * Embedded Schemas
5
+ * ---------------------------------- */
6
+
7
+ const orderTicketSchema = new mongoose.Schema(
8
+ {
9
+ ticketVariantId: {
10
+ type: mongoose.Schema.Types.ObjectId,
11
+ required: true,
12
+ ref: "TicketVariant"
13
+ },
14
+ variantName: {
15
+ type: String,
16
+ required: true,
17
+ trim: true
18
+ },
19
+ quantity: {
20
+ type: Number,
21
+ required: true,
22
+ min: 1
23
+ },
24
+ pricePerTicket: {
25
+ type: Number,
26
+ required: true,
27
+ min: 0
28
+ },
29
+ subtotal: {
30
+ type: Number,
31
+ required: true,
32
+ min: 0
33
+ }
34
+ },
35
+ { _id: false }
36
+ );
37
+
38
+ const pricingSchema = new mongoose.Schema(
39
+ {
40
+ subtotal: { type: Number, required: true, min: 0 },
41
+ platformFee: { type: Number, required: true, min: 0 },
42
+ paymentFee: { type: Number, required: true, min: 0 },
43
+ total: { type: Number, required: true, min: 0 },
44
+ currency: { type: String, default: "BDT" },
45
+ hostPayout: { type: Number, required: true, min: 0 }
46
+ },
47
+ { _id: false }
48
+ );
49
+
50
+ const refundSchema = new mongoose.Schema(
51
+ {
52
+ reason: {
53
+ type: String,
54
+ enum: ["event_cancelled", "user_request", "fraud"],
55
+ required: true
56
+ },
57
+ amount: {
58
+ type: Number,
59
+ required: true,
60
+ min: 0
61
+ },
62
+ refundedAt: {
63
+ type: Date,
64
+ default: Date.now
65
+ },
66
+ stripeRefundId: String
67
+ },
68
+ { _id: false }
69
+ );
70
+
71
+ /* ----------------------------------
72
+ * Main Order Schema
73
+ * ---------------------------------- */
74
+
75
+ const orderSchema = new mongoose.Schema(
76
+ {
77
+ /* ---------- Identity ---------- */
78
+
79
+ orderNumber: {
80
+ type: String,
81
+ unique: true,
82
+ index: true
83
+ },
84
+
85
+ userId: {
86
+ type: mongoose.Schema.Types.ObjectId,
87
+ ref: "User",
88
+ required: true,
89
+ index: true
90
+ },
91
+
92
+ eventId: {
93
+ type: mongoose.Schema.Types.ObjectId,
94
+ ref: "Event",
95
+ required: true,
96
+ index: true
97
+ },
98
+
99
+ /* ---------- Tickets ---------- */
100
+
101
+ tickets: {
102
+ type: [orderTicketSchema],
103
+ validate: (v: any) => v.length > 0
104
+ },
105
+
106
+ ticketCount: {
107
+ type: Number,
108
+ required: true,
109
+ min: 1
110
+ },
111
+
112
+ ticketIds: [{
113
+ type: mongoose.Schema.Types.ObjectId,
114
+ ref: "Ticket"
115
+ }],
116
+
117
+ /* ---------- Pricing ---------- */
118
+
119
+ pricing: {
120
+ type: pricingSchema,
121
+ required: true
122
+ },
123
+
124
+ /* ---------- Payment ---------- */
125
+
126
+ paymentId: String,
127
+
128
+ paymentMethod: {
129
+ type: String,
130
+ enum: ["card", "bkash", "bank_transfer", "free"],
131
+ required: true
132
+ },
133
+
134
+ paymentStatus: {
135
+ type: String,
136
+ enum: ["pending", "succeeded", "failed"],
137
+ default: "pending",
138
+ index: true
139
+ },
140
+
141
+ paidAt: Date,
142
+
143
+ /* ---------- Order Lifecycle ---------- */
144
+
145
+ status: {
146
+ type: String,
147
+ enum: ["pending", "confirmed", "cancelled", "refunded"],
148
+ default: "pending",
149
+ index: true
150
+ },
151
+
152
+ requiresManualReview: {
153
+ type: Boolean,
154
+ default: false
155
+ },
156
+
157
+ manualReviewReason: String,
158
+
159
+ confirmedAt: Date,
160
+ cancelledAt: Date,
161
+ refundedAt: Date,
162
+
163
+ expiresAt: {
164
+ type: Date,
165
+ required: true,
166
+ index: true
167
+ },
168
+
169
+ reminderSentAt: Date,
170
+
171
+ /* ---------- Buyer ---------- */
172
+
173
+ buyerEmail: {
174
+ type: String,
175
+ required: true,
176
+ lowercase: true,
177
+ trim: true,
178
+ index: true
179
+ },
180
+
181
+ buyerPhone: String,
182
+
183
+ /* ---------- Refund ---------- */
184
+
185
+ refund: {
186
+ type: refundSchema,
187
+ validate: {
188
+ validator(refund: any) {
189
+ return !refund || refund.amount <= this.pricing.total;
190
+ },
191
+ message: "Refund amount exceeds order total"
192
+ }
193
+ },
194
+
195
+ /* ---------- Audit ---------- */
196
+
197
+ ipAddress: String,
198
+ userAgent: String
199
+ },
200
+ {
201
+ timestamps: true,
202
+ strict: true
203
+ }
204
+ );
205
+
206
+ /* ----------------------------------
207
+ * Indexes
208
+ * ---------------------------------- */
209
+
210
+ orderSchema.index({ status: 1, createdAt: -1 });
211
+ orderSchema.index({ eventId: 1, status: 1, createdAt: -1 });
212
+ orderSchema.index({ paymentStatus: 1, createdAt: -1 });
213
+ orderSchema.index({ expiresAt: 1 }, {
214
+ expireAfterSeconds: 0,
215
+ partialFilterExpression: { status: "pending" }
216
+ });
217
+
218
+ /* ----------------------------------
219
+ * Hooks
220
+ * ---------------------------------- */
221
+
222
+ // Generate order number
223
+ orderSchema.pre("save", function (next) {
224
+ if (this.isNew && !this.orderNumber) {
225
+ const ts = Date.now().toString().slice(-6);
226
+ const rand = Math.random().toString(36).substring(2, 8).toUpperCase();
227
+ this.orderNumber = `ORD-${ts}-${rand}`;
228
+ }
229
+ next();
230
+ });
231
+
232
+ // Compute ticket count
233
+ orderSchema.pre("validate", function (next) {
234
+ if (this.tickets?.length) {
235
+ this.ticketCount = this.tickets.reduce((sum, t) => sum + t.quantity, 0);
236
+ }
237
+ next();
238
+ });
239
+
240
+ // Pricing immutability after confirmation
241
+ orderSchema.pre("save", function (next) {
242
+ if (!this.isNew && this.isModified("pricing") && this.status !== "pending") {
243
+ return next(new Error("Pricing cannot be modified after confirmation"));
244
+ }
245
+ next();
246
+ });
247
+
248
+ /* ----------------------------------
249
+ * Export
250
+ * ---------------------------------- */
251
+
252
+ export default mongoose.model("Order", orderSchema);
253
+