@feardread/fear 1.1.4 → 1.1.6

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.
@@ -1,31 +1,191 @@
1
1
  const Razorpay = require("razorpay");
2
+ const paypal = require("@paypal/checkout-server-sdk");
3
+ const Payment = require("../models/payment");
4
+ const methods = require("./crud");
5
+
2
6
  const instance = new Razorpay({
3
7
  key_id: "rzp_test_HSSeDI22muUrLR",
4
8
  key_secret: "sRO0YkBxvgMg0PvWHJN16Uf7",
5
9
  });
6
10
 
7
- const checkout = async (req, res) => {
8
- const { amount } = req.body;
9
- const option = {
10
- amount: amount * 100,
11
- currency: "INR",
12
- };
13
- const order = await instance.orders.create(option);
14
- res.json({
15
- success: true,
16
- order,
17
- });
11
+ // Razorpay instance
12
+ const razorpayInstance = new Razorpay({
13
+ key_id: "rzp_test_HSSeDI22muUrLR",
14
+ key_secret: "sRO0YkBxvgMg0PvWHJN16Uf7",
15
+ });
16
+
17
+ // PayPal environment setup
18
+ const paypalEnvironment = () => {
19
+ const clientId = process.env.PAYPAL_CLIENT_ID || "YOUR_PAYPAL_CLIENT_ID";
20
+ const clientSecret = process.env.PAYPAL_CLIENT_SECRET || "YOUR_PAYPAL_CLIENT_SECRET";
21
+
22
+ // Use sandbox for testing, live for production
23
+ return new paypal.core.SandboxEnvironment(clientId, clientSecret);
24
+ // For production: return new paypal.core.LiveEnvironment(clientId, clientSecret);
18
25
  };
19
26
 
20
- const paymentVerification = async (req, res) => {
21
- const { razorpayOrderId, razorpayPaymentId } = req.body;
22
- res.json({
23
- razorpayOrderId,
24
- razorpayPaymentId,
25
- });
27
+ const paypalClient = () => {
28
+ return new paypal.core.PayPalHttpClient(paypalEnvironment());
26
29
  };
27
30
 
28
- module.exports = {
29
- checkout,
30
- paymentVerification,
31
+ exports.checkout = async (req, res) => {
32
+ try {
33
+ const { amount } = req.body;
34
+ const option = {
35
+ amount: amount * 100,
36
+ currency: "INR",
37
+ };
38
+ const order = await razorpayInstance.orders.create(option);
39
+ res.json({
40
+ success: true,
41
+ order,
42
+ });
43
+ } catch (error) {
44
+ res.status(500).json({
45
+ success: false,
46
+ message: "Error creating Razorpay order",
47
+ error: error.message,
48
+ });
49
+ }
31
50
  };
51
+
52
+ exports.paymentVerification = async (req, res) => {
53
+ try {
54
+ const { razorpayOrderId, razorpayPaymentId, razorpaySignature } = req.body;
55
+
56
+ // Verify signature for security
57
+ const crypto = require("crypto");
58
+ const generatedSignature = crypto
59
+ .createHmac("sha256", "sRO0YkBxvgMg0PvWHJN16Uf7")
60
+ .update(`${razorpayOrderId}|${razorpayPaymentId}`)
61
+ .digest("hex");
62
+
63
+ if (generatedSignature === razorpaySignature) {
64
+ res.json({
65
+ success: true,
66
+ razorpayOrderId,
67
+ razorpayPaymentId,
68
+ verified: true,
69
+ });
70
+ } else {
71
+ res.status(400).json({
72
+ success: false,
73
+ message: "Payment verification failed",
74
+ });
75
+ }
76
+ } catch (error) {
77
+ res.status(500).json({
78
+ success: false,
79
+ message: "Error verifying payment",
80
+ error: error.message,
81
+ });
82
+ }
83
+ };
84
+
85
+ exports.createStripePayment = async (req, res) => {
86
+ try {
87
+ const { paymentMethodId, amount, currency } = req.body;
88
+
89
+ // Create a PaymentIntent
90
+ const paymentIntent = await stripe.paymentIntents.create({
91
+ amount: amount,
92
+ currency: currency || 'usd',
93
+ payment_method: paymentMethodId,
94
+ confirmation_method: 'manual',
95
+ confirm: true,
96
+ return_url: 'https://your-site.com/payment-success',
97
+ });
98
+
99
+ res.json({
100
+ clientSecret: paymentIntent.client_secret,
101
+ status: paymentIntent.status,
102
+ });
103
+ } catch (error) {
104
+ res.status(400).json({ error: error.message });
105
+ }
106
+ };
107
+ exports.saveStripePayment = async (req, res) => {
108
+ try {
109
+ const { paymentMethodId, customerId } = req.body;
110
+
111
+ // Attach payment method to customer
112
+ await stripe.paymentMethods.attach(paymentMethodId, {
113
+ customer: customerId,
114
+ });
115
+
116
+ // Set as default payment method
117
+ await stripe.customers.update(customerId, {
118
+ invoice_settings: {
119
+ default_payment_method: paymentMethodId,
120
+ },
121
+ });
122
+
123
+ res.json({ success: true });
124
+ } catch (error) {
125
+ res.status(400).json({ error: error.message });
126
+ }
127
+ }
128
+ // ============ PAYPAL METHODS ============
129
+
130
+ exports.createPayPalOrder = async (req, res) => {
131
+ try {
132
+ const { amount, currency = "USD" } = req.body;
133
+
134
+ const request = new paypal.orders.OrdersCreateRequest();
135
+ request.prefer("return=representation");
136
+ request.requestBody({
137
+ intent: "CAPTURE",
138
+ purchase_units: [{
139
+ amount: {
140
+ currency_code: currency,
141
+ value: amount.toString(),
142
+ },
143
+ }],
144
+ });
145
+
146
+ const order = await paypalClient().execute(request);
147
+
148
+ res.json({
149
+ success: true,
150
+ orderId: order.result.id,
151
+ order: order.result,
152
+ });
153
+ } catch (error) {
154
+ res.status(500).json({
155
+ success: false,
156
+ message: "Error creating PayPal order",
157
+ error: error.message,
158
+ });
159
+ }
160
+ };
161
+
162
+ exports.capturePayPalOrder = async (req, res) => {
163
+ try {
164
+ const { orderId } = req.body;
165
+
166
+ const request = new paypal.orders.OrdersCaptureRequest(orderId);
167
+ request.requestBody({});
168
+
169
+ const capture = await paypalClient().execute(request);
170
+
171
+ res.json({
172
+ success: true,
173
+ captureId: capture.result.id,
174
+ status: capture.result.status,
175
+ capture: capture.result,
176
+ });
177
+ } catch (error) {
178
+ res.status(500).json({
179
+ success: false,
180
+ message: "Error capturing PayPal order",
181
+ error: error.message,
182
+ });
183
+ }
184
+ };
185
+
186
+ const crud = methods.crudController( Payment );
187
+ for(prop in crud) {
188
+ if(crud.hasOwnProperty(prop)) {
189
+ module.exports[prop] = crud[prop];
190
+ }
191
+ }
package/libs/db/index.js CHANGED
@@ -41,6 +41,11 @@ class DatabaseManager {
41
41
  await this.close();
42
42
  process.exit(0);
43
43
  });
44
+
45
+ process.on('UnhandledRejection', async (promise, reson) => {
46
+ console.log('Unhandled Rejection at ', reason);
47
+ await this.close();
48
+ })
44
49
  }
45
50
 
46
51
  /**
@@ -52,7 +52,7 @@ exports.input = {
52
52
  * @returns {object} Validation result
53
53
  */
54
54
  register: (data) => {
55
- const { email, password, firstname, lastname, name } = data;
55
+ const { email, password, firstName, lastName, displayName } = data;
56
56
 
57
57
  if (!email) return { isValid: false, message: "Email is required" };
58
58
  if (!exports.input.email(email)) return { isValid: false, message: "Please provide a valid email address" };
@@ -60,7 +60,7 @@ exports.input = {
60
60
  const passwordValidation = exports.input.password(password);
61
61
  if (!passwordValidation.isValid) return passwordValidation;
62
62
 
63
- const fullName = firstname && lastname ? `${firstname} ${lastname}` : name;
63
+ const fullName = (displayName) ? displayName : `${firstName} ${lastName}`;
64
64
  if (!fullName) return { isValid: false, message: "Name is required" };
65
65
 
66
66
  return { isValid: true, name: fullName };
@@ -0,0 +1,37 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const addressSchema = new mongoose.Schema({
4
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
5
+ type: { type: String, enum: ['home', 'work', 'billing', 'shipping', 'other'], default: 'billing' },
6
+ isDefault: { type: Boolean, default: false },
7
+ name: { type: String, trim: true, maxlength: 50 },
8
+ line1: { type: String, required: true, trim: true },
9
+ line2: { type: String, trim: true },
10
+ city: { type: String, required: true, trim: true },
11
+ state: { type: String, required: true, trim: true },
12
+ postalCode: { type: String, required: true, trim: true },
13
+ country: { type: String, required: true, default: 'US', uppercase: true },
14
+ coordinates: { type: { type: String, enum: ['Point'], default: 'Point' },
15
+ coordinates: { type: [Number], default: [0, 0] }},
16
+ phoneNumber: { type: String, trim: true },
17
+ deliveryInstructions: { type: String, trim: true, maxlength: 500 }
18
+ }, {
19
+ timestamps: true
20
+ });
21
+
22
+ // Indexes
23
+ addressSchema.index({ userId: 1, isDefault: 1 });
24
+ addressSchema.index({ coordinates: '2dsphere' });
25
+
26
+ // Ensure only one default address per user
27
+ addressSchema.pre('save', async function(next) {
28
+ if (this.isDefault) {
29
+ await this.constructor.updateMany(
30
+ { userId: this.userId, _id: { $ne: this._id } },
31
+ { $set: { isDefault: false } }
32
+ );
33
+ }
34
+ next();
35
+ });
36
+
37
+ module.exports = mongoose.model('Address', addressSchema);
package/models/order.js CHANGED
@@ -1,88 +1,116 @@
1
- const mongoose = require("mongoose"); // Erase if already required
1
+ const mongoose = require("mongoose");
2
2
 
3
- // Declare the Schema of the Mongo model
4
- var orderSchema = new mongoose.Schema(
3
+ const orderItemSchema = new mongoose.Schema({
4
+ productId: { type: mongoose.Schema.Types.ObjectId, ref: "Product", required: true },
5
+ title: { type: String, required: true, },
6
+ quantity: { type: Number, required: true, min: 1, },
7
+ price: { type: Number, required: true, },
8
+ discount: { type: Number, default: 0, },
9
+ tax: { type: Number, default: 0, },
10
+ subtotal: { type: Number, required: true, },
11
+ image: String,
12
+ sku: String,
13
+ attributes: { type: Map, of: String, },
14
+ });
15
+
16
+ const orderSchema = new mongoose.Schema(
5
17
  {
6
- user: {
7
- type: mongoose.Schema.Types.ObjectId,
8
- ref: "User",
9
- required: true,
18
+ orderNumber: { type: String, unique: true, required: true, },
19
+ userId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true, index: true, },
20
+ items: [orderItemSchema],
21
+ subtotal: { type: Number, required: true },
22
+ discount: { type: Number, default: 0, },
23
+ tax: { type: Number, default: 0, },
24
+ shippingCost: { type: Number, default: 0, },
25
+ total: { type: Number, required: true, },
26
+ currency: { type: String, default: "INR", enum: ["INR", "USD", "EUR", "GBP"], },
27
+ paymentMethod: { type: String, enum: ["razorpay", "paypal", "card", "upi", "netbanking", "cod"], required: true, },
28
+ paymentStatus: {
29
+ type: String,
30
+ enum: ["pending", "processing", "completed", "failed", "refunded", "partially_refunded"],
31
+ default: "pending",
32
+ index: true,
10
33
  },
11
- shippingInfo: {
12
- firstname: {
13
- type: String,
14
- required: true,
15
- },
16
- lastname: {
17
- type: String,
18
- required: true,
19
- },
20
- address: {
21
- type: String,
22
- required: true,
23
- },
24
- city: {
25
- type: String,
26
- required: true,
27
- },
28
- state: {
29
- type: String,
30
- required: true,
31
- },
32
- other: {
33
- type: String,
34
- },
35
- pincode: {
36
- type: Number,
37
- required: true,
34
+ paymentDetails: {
35
+ // Razorpay
36
+ razorpayOrderId: String,
37
+ razorpayPaymentId: String,
38
+ razorpaySignature: String,
39
+
40
+ // PayPal
41
+ paypalOrderId: String,
42
+ paypalCaptureId: String,
43
+
44
+ // Common
45
+ transactionId: String,
46
+ paymentMethodId: {
47
+ type: mongoose.Schema.Types.ObjectId,
48
+ ref: "PaymentMethod",
38
49
  },
50
+ paidAt: Date,
39
51
  },
40
- paymentInfo: {
41
- razorpayOrderId: {
42
- type: String,
43
- required: true,
44
- },
45
- razorpayPaymentId: {
46
- type: String,
47
- required: true,
48
- },
52
+ orderStatus: {
53
+ type: String,
54
+ enum: [
55
+ "pending",
56
+ "confirmed",
57
+ "processing",
58
+ "shipped",
59
+ "out_for_delivery",
60
+ "delivered",
61
+ "cancelled",
62
+ "returned",
63
+ "refunded"
64
+ ],
65
+ default: "pending",
66
+ index: true,
49
67
  },
50
- orderItems: [
68
+ shippingAddress: { type: mongoose.Schema.Types.ObjectId, ref: "Address", required: true, },
69
+ billingAddress: { type: mongoose.Schema.Types.ObjectId, ref: "Address", required: true, },
70
+ shippingProvider: String,
71
+ trackingNumber: String,
72
+ estimatedDeliveryDate: Date,
73
+ actualDeliveryDate: Date,
74
+ statusHistory: [
51
75
  {
52
- product: {
53
- type: mongoose.Schema.Types.ObjectId,
54
- ref: "Product",
55
- required: true,
76
+ status: String,
77
+ timestamp: {
78
+ type: Date,
79
+ default: Date.now,
56
80
  },
57
- quantity: {
58
- type: Number,
59
- required: true,
60
- },
61
- price: {
62
- type: Number,
63
- required: true,
81
+ note: String,
82
+ updatedBy: {
83
+ type: mongoose.Schema.Types.ObjectId,
84
+ ref: "User",
64
85
  },
65
86
  },
66
87
  ],
67
- paidAt: {
68
- type: Date,
69
- default: Date.now(),
70
- },
71
- month: {
88
+ couponCode: String,
89
+ couponDiscount: {
72
90
  type: Number,
73
- default: new Date().getMonth(),
91
+ default: 0,
74
92
  },
75
- totalPrice: {
76
- type: Number,
77
- required: true,
78
- },
79
- totalPriceAfterDiscount: {
80
- type: Number,
81
- required: true,
93
+ customerNote: String,
94
+ internalNote: String,
95
+ cancellationReason: String,
96
+ cancelledAt: Date,
97
+ cancelledBy: {
98
+ type: mongoose.Schema.Types.ObjectId,
99
+ ref: "User",
82
100
  },
83
- orderStatus: {
101
+ returnReason: String,
102
+ returnedAt: Date,
103
+ refundAmount: Number,
104
+ refundStatus: {
84
105
  type: String,
85
- default: "Ordered",
106
+ enum: ["none", "pending", "processing", "completed", "failed"],
107
+ default: "none",
108
+ },
109
+ refundedAt: Date,
110
+ refundTransactionId: String,
111
+ metadata: {
112
+ type: Map,
113
+ of: mongoose.Schema.Types.Mixed,
86
114
  },
87
115
  },
88
116
  {
@@ -90,5 +118,154 @@ var orderSchema = new mongoose.Schema(
90
118
  }
91
119
  );
92
120
 
93
- //Export the model
94
- module.exports = mongoose.model("Order", orderSchema);
121
+ // Indexes for better query performance
122
+ orderSchema.index({ orderNumber: 1 });
123
+ orderSchema.index({ userId: 1, createdAt: -1 });
124
+ orderSchema.index({ orderStatus: 1, createdAt: -1 });
125
+ orderSchema.index({ paymentStatus: 1 });
126
+ orderSchema.index({ "paymentDetails.razorpayOrderId": 1 });
127
+ orderSchema.index({ "paymentDetails.paypalOrderId": 1 });
128
+ orderSchema.index({ trackingNumber: 1 });
129
+ orderSchema.index({ shippingAddress: 1 });
130
+ orderSchema.index({ billingAddress: 1 });
131
+
132
+ // Pre-save middleware to generate order number
133
+ orderSchema.pre("save", async function (next) {
134
+ if (!this.orderNumber) {
135
+ const timestamp = Date.now().toString(36).toUpperCase();
136
+ const random = Math.random().toString(36).substring(2, 7).toUpperCase();
137
+ this.orderNumber = `ORD-${timestamp}-${random}`;
138
+ }
139
+ next();
140
+ });
141
+
142
+ // Pre-save middleware to add status history
143
+ orderSchema.pre("save", function (next) {
144
+ if (this.isModified("orderStatus")) {
145
+ this.statusHistory.push({
146
+ status: this.orderStatus,
147
+ timestamp: new Date(),
148
+ });
149
+ }
150
+ next();
151
+ });
152
+
153
+ // Static method to get orders by user
154
+ orderSchema.statics.getUserOrders = async function (userId, options = {}) {
155
+ const { status, limit = 10, skip = 0 } = options;
156
+ const query = { userId };
157
+
158
+ if (status) {
159
+ query.orderStatus = status;
160
+ }
161
+
162
+ return await this.find(query)
163
+ .sort({ createdAt: -1 })
164
+ .limit(limit)
165
+ .skip(skip)
166
+ .populate("items.productId", "name images")
167
+ .populate("paymentDetails.paymentMethodId")
168
+ .populate("shippingAddress")
169
+ .populate("billingAddress");
170
+ };
171
+
172
+ // Static method to get order statistics
173
+ orderSchema.statics.getOrderStats = async function (userId) {
174
+ const stats = await this.aggregate([
175
+ { $match: { userId: mongoose.Types.ObjectId(userId) } },
176
+ {
177
+ $group: {
178
+ _id: "$orderStatus",
179
+ count: { $sum: 1 },
180
+ totalAmount: { $sum: "$total" },
181
+ },
182
+ },
183
+ ]);
184
+
185
+ return stats;
186
+ };
187
+
188
+ // Instance method to mark as paid
189
+ orderSchema.methods.markAsPaid = async function (paymentInfo) {
190
+ this.paymentStatus = "completed";
191
+ this.paymentDetails = {
192
+ ...this.paymentDetails,
193
+ ...paymentInfo,
194
+ paidAt: new Date(),
195
+ };
196
+ this.orderStatus = "confirmed";
197
+ return await this.save();
198
+ };
199
+
200
+ // Instance method to update order status
201
+ orderSchema.methods.updateStatus = async function (status, note, updatedBy) {
202
+ this.orderStatus = status;
203
+ this.statusHistory.push({
204
+ status,
205
+ timestamp: new Date(),
206
+ note,
207
+ updatedBy,
208
+ });
209
+
210
+ // Update specific dates based on status
211
+ if (status === "delivered") {
212
+ this.actualDeliveryDate = new Date();
213
+ } else if (status === "cancelled") {
214
+ this.cancelledAt = new Date();
215
+ this.cancelledBy = updatedBy;
216
+ } else if (status === "returned") {
217
+ this.returnedAt = new Date();
218
+ }
219
+
220
+ return await this.save();
221
+ };
222
+
223
+ // Instance method to process refund
224
+ orderSchema.methods.processRefund = async function (amount, transactionId) {
225
+ this.refundAmount = amount || this.total;
226
+ this.refundStatus = "completed";
227
+ this.refundedAt = new Date();
228
+ this.refundTransactionId = transactionId;
229
+ this.paymentStatus = amount >= this.total ? "refunded" : "partially_refunded";
230
+ this.orderStatus = "refunded";
231
+ return await this.save();
232
+ };
233
+
234
+ // Instance method to add tracking information
235
+ orderSchema.methods.addTracking = async function (provider, trackingNumber, estimatedDelivery) {
236
+ this.shippingProvider = provider;
237
+ this.trackingNumber = trackingNumber;
238
+ this.estimatedDeliveryDate = estimatedDelivery;
239
+ this.orderStatus = "shipped";
240
+ return await this.save();
241
+ };
242
+
243
+ // Virtual for order age in days
244
+ orderSchema.virtual("orderAge").get(function () {
245
+ return Math.floor((Date.now() - this.createdAt) / (1000 * 60 * 60 * 24));
246
+ });
247
+
248
+ // Virtual for items count
249
+ orderSchema.virtual("itemsCount").get(function () {
250
+ return this.items.reduce((total, item) => total + item.quantity, 0);
251
+ });
252
+
253
+ // Virtual for checking if order can be cancelled
254
+ orderSchema.virtual("canCancel").get(function () {
255
+ return ["pending", "confirmed", "processing"].includes(this.orderStatus);
256
+ });
257
+
258
+ // Virtual for checking if order can be returned
259
+ orderSchema.virtual("canReturn").get(function () {
260
+ const returnWindow = 7; // days
261
+ return (
262
+ this.orderStatus === "delivered" &&
263
+ this.orderAge <= returnWindow
264
+ );
265
+ });
266
+
267
+ // Ensure virtuals are included in JSON
268
+ orderSchema.set("toJSON", { virtuals: true });
269
+ orderSchema.set("toObject", { virtuals: true });
270
+
271
+ module.exports = mongoose.model("Order", orderSchema);