krsyer-server-monitor-pro 1.0.11 → 1.0.13

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.
@@ -4,6 +4,7 @@ const { Cashfree, CFEnvironment } = require('cashfree-pg');
4
4
  const path = require('path');
5
5
  const cors = require('cors');
6
6
  const fs = require('fs');
7
+ const { sendLicenseEmail } = require('./services/emailService');
7
8
 
8
9
  const app = express();
9
10
  app.use(express.static('public'));
@@ -111,7 +112,7 @@ app.post('/api/buy', async (req, res) => {
111
112
  customer_email: email
112
113
  },
113
114
  order_meta: {
114
- return_url: `${process.env.APP_URL}/verify?order_id={order_id}`
115
+ return_url: `${process.env.APP_URL}/verify?order_id={order_id}&email=${encodeURIComponent(email)}&phone=${encodeURIComponent(phone)}`
115
116
  }
116
117
  };
117
118
 
@@ -127,20 +128,22 @@ app.post('/api/buy', async (req, res) => {
127
128
 
128
129
  // 3. Verify Route (Return URL)
129
130
  app.get('/verify', async (req, res) => {
130
- const { order_id } = req.query;
131
+ const { order_id, email, phone } = req.query;
132
+
133
+ if (!order_id) {
134
+ return res.redirect('/');
135
+ }
136
+
131
137
  try {
132
138
  const cf = getCashfree();
139
+ // Check Payment Status
133
140
  const response = await cf.PGOrderFetchPayments(order_id);
134
141
  const payments = response.data;
142
+
143
+ // Find successful payment
135
144
  const isPaid = payments.some(p => p.payment_status === 'SUCCESS');
136
145
 
137
146
  if (isPaid) {
138
- // Get Customer Details from Order Fetch?
139
- // To simplify, we get email from stored order or just query Cashfree Order details again
140
- const orderDetails = await cf.PGOrderFetch(order_id);
141
- const email = orderDetails.data.customer_details.customer_email;
142
- const phone = orderDetails.data.customer_details.customer_phone;
143
-
144
147
  // Generate License
145
148
  const licenseKey = 'PRO-' + Math.random().toString(36).substring(2, 10).toUpperCase() + '-' + Date.now().toString(36).toUpperCase();
146
149
 
@@ -148,44 +151,35 @@ app.get('/verify', async (req, res) => {
148
151
  const expiryDate = new Date();
149
152
  expiryDate.setFullYear(expiryDate.getFullYear() + 1);
150
153
 
151
- // Save to MySQL
154
+ // Check if license already exists for this order (Idempotency)
152
155
  if (pool) {
156
+ const [existing] = await pool.query('SELECT license_key FROM licenses WHERE order_id = ?', [order_id]);
157
+ if (existing.length > 0) {
158
+ return res.send(getSuccessHtml(existing[0].license_key, order_id, true));
159
+ }
160
+
153
161
  await pool.query(
154
162
  'INSERT INTO licenses (license_key, order_id, customer_email, customer_phone, expiry_date, active) VALUES (?, ?, ?, ?, ?, ?)',
155
- [licenseKey, order_id, email, phone, expiryDate, true]
163
+ [licenseKey, order_id, email || 'Unknown', phone || 'Unknown', expiryDate, true]
156
164
  );
165
+
166
+ // Send Email Notification
167
+ if (email && email !== 'Unknown') {
168
+ // Fire and forget, or await? Let's await to log success
169
+ await sendLicenseEmail(email, licenseKey, expiryDate);
170
+ }
157
171
  } else {
158
172
  console.error("Critical: DB Pool missing during verify. License generated but not saved:", licenseKey);
159
173
  }
160
174
 
161
175
  // Show Success Page
162
- res.send(`
163
- <html>
164
- <body style="font-family: sans-serif; text-align: center; padding: 50px; background: #0f172a; color: white;">
165
- <div style="max-width: 600px; margin: auto; background: #1e293b; padding: 40px; border-radius: 12px; border: 1px solid #334155;">
166
- <h1 style="color: #4ade80;">Payment Successful!</h1>
167
- <p style="color: #94a3b8; font-size: 18px;">Thank you for your yearly subscription.</p>
168
-
169
- <div style="background: #0f172a; padding: 20px; margin: 30px 0; border-radius: 8px; border: 1px dashed #4ade80;">
170
- <p style="margin: 0; color: #94a3b8; font-size: 14px;">YOUR LICENSE KEY (Valid until ${expiryDate.toDateString()})</p>
171
- <h2 style="margin: 10px 0; letter-spacing: 2px; font-family: monospace;">${licenseKey}</h2>
172
- </div>
173
-
174
- <p style="color: #cbd5e1;">Copy this key and paste it into your server <code>.env</code> file:</p>
175
- <code style="background: black; padding: 10px; display: block; text-align: left; color: #fbbf24;">LICENSE_KEY=${licenseKey}</code>
176
-
177
- <br>
178
- <a href="/" style="color: white; text-decoration: none; border-bottom: 1px solid white;">Back Home</a>
179
- </div>
180
- </body>
181
- </html>
182
- `);
176
+ res.send(getSuccessHtml(licenseKey, order_id, false));
183
177
  } else {
184
178
  res.send(`<h1>Payment Failed or Pending</h1><a href="/">Try Again</a>`);
185
179
  }
186
180
  } catch (e) {
187
- console.error("Cashfree Verification Error:", e.response?.data || e.message);
188
- res.status(500).send("Verification Error");
181
+ console.error("Cashfree Verification Error:", e);
182
+ res.status(500).send(`<h1>Verification Error</h1><p>${e.message}</p><pre>${JSON.stringify(e.response?.data || {}, null, 2)}</pre>`);
189
183
  }
190
184
  });
191
185
 
@@ -232,5 +226,60 @@ app.get('/api/validate', async (req, res) => {
232
226
  return res.json({ valid: false, message: 'Invalid Key' });
233
227
  });
234
228
 
229
+ const getSuccessHtml = (licenseKey, orderId, isExisting) => {
230
+ return `
231
+ <!DOCTYPE html>
232
+ <html lang="en">
233
+ <head>
234
+ <meta charset="UTF-8">
235
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
236
+ <title>License Activated</title>
237
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
238
+ <style>
239
+ body { font-family: 'Space Grotesk', sans-serif; background: #0f172a; color: white; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; }
240
+ .card { background: #1e293b; padding: 2.5rem; border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); text-align: center; max-width: 500px; width: 90%; border: 1px solid #334155; }
241
+ h1 { color: ${isExisting ? '#fbbf24' : '#4ade80'}; margin-bottom: 0.5rem; font-size: 2rem; }
242
+ .key-box { background: rgba(0,0,0,0.3); padding: 15px; border-radius: 8px; margin: 20px 0; border: 1px dashed #475569; position: relative; }
243
+ .label { color: #94a3b8; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 5px; display: block; }
244
+ .value { font-family: monospace; font-size: 1.25rem; color: #fbbf24; word-break: break-all; font-weight: bold; }
245
+ .credentials { background: #334155; padding: 15px; border-radius: 8px; margin-top: 20px; text-align: left; }
246
+ .cred-row { display: flex; justify-content: space-between; margin-bottom: 8px; font-size: 0.95rem; }
247
+ .cred-label { color: #cbd5e1; }
248
+ .cred-val { color: #fff; font-weight: 600; font-family: monospace; }
249
+ .warning { color: #f87171; font-size: 0.85rem; margin-top: 10px; font-style: italic; text-align: center;}
250
+ .btn { display: inline-block; margin-top: 25px; padding: 12px 24px; background: #3b82f6; color: white; text-decoration: none; border-radius: 8px; font-weight: 600; transition: all 0.2s; }
251
+ .btn:hover { background: #2563eb; transform: translateY(-1px); }
252
+ </style>
253
+ </head>
254
+ <body>
255
+ <div class="card">
256
+ <h1>${isExisting ? 'License Retrieved' : 'Payment Successful!'}</h1>
257
+ <p style="color: #cbd5e1;">Your License is active.</p>
258
+
259
+ <div class="key-box">
260
+ <span class="label">License Key</span>
261
+ <div class="value">${licenseKey}</div>
262
+ </div>
263
+
264
+ <div class="credentials">
265
+ <div style="margin-bottom:12px; color:#60a5fa; font-weight:bold; font-size:0.95rem; text-align:center; text-transform:uppercase; letter-spacing:1px;">Monitor Credentials</div>
266
+ <div class="cred-row">
267
+ <span class="cred-label">Username:</span>
268
+ <span class="cred-val">admin@monitor.com</span>
269
+ </div>
270
+ <div class="cred-row">
271
+ <span class="cred-label">Password:</span>
272
+ <span class="cred-val">AdminPass123!</span>
273
+ </div>
274
+ <div class="warning">⚠️ Please change your password after initial login</div>
275
+ </div>
276
+
277
+ <a href="/" class="btn">Back Home</a>
278
+ </div>
279
+ </body>
280
+ </html>
281
+ `;
282
+ };
283
+
235
284
  const PORT = process.env.PORT || 3011;
236
285
  app.listen(PORT, () => console.log(`License Portal running on port ${PORT}`));
@@ -0,0 +1,126 @@
1
+ const nodemailer = require('nodemailer');
2
+
3
+ const transporter = nodemailer.createTransport({
4
+ host: process.env.SMTP_HOST,
5
+ port: parseInt(process.env.SMTP_PORT || '465'),
6
+ secure: process.env.SMTP_SECURE === 'true',
7
+ auth: {
8
+ user: process.env.SMTP_USER,
9
+ pass: process.env.SMTP_PASS
10
+ }
11
+ });
12
+
13
+ /**
14
+ * Sends the license confirmation email
15
+ * @param {string} to - Recipient email
16
+ * @param {string} licenseKey - The generated license key
17
+ * @param {string} expiryDate - Expiry date string
18
+ */
19
+ const sendLicenseEmail = async (to, licenseKey, expiryDate) => {
20
+ const subject = '🚀 Your Server Monitor Pro License + Credentials';
21
+
22
+ // Formatting date
23
+ const dateStr = new Date(expiryDate).toLocaleDateString('en-US', {
24
+ year: 'numeric', month: 'long', day: 'numeric'
25
+ });
26
+
27
+ const html = `
28
+ <!DOCTYPE html>
29
+ <html>
30
+ <head>
31
+ <meta charset="utf-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>License Key</title>
34
+ </head>
35
+ <body style="margin: 0; padding: 0; background-color: #f1f5f9; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
36
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="min-width: 100%;">
37
+ <tr>
38
+ <td align="center" style="padding: 40px 0;">
39
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="600" style="background-color: #ffffff; border-radius: 16px; box-shadow: 0 10px 25px rgba(0,0,0,0.05); overflow: hidden;">
40
+ <!-- Header -->
41
+ <tr>
42
+ <td align="center" style="background: linear-gradient(135deg, #4f46e5, #7c3aed); padding: 40px 0;">
43
+ <h1 style="color: #ffffff; margin: 0; font-size: 28px; letter-spacing: 1px;">Server Monitor Pro</h1>
44
+ <p style="color: rgba(255,255,255,0.9); margin-top: 10px; font-size: 16px;">Thank you for your purchase!</p>
45
+ </td>
46
+ </tr>
47
+
48
+ <!-- Content -->
49
+ <tr>
50
+ <td style="padding: 40px;">
51
+ <h2 style="color: #1e293b; margin-top: 0; font-size: 20px;">Your License is Ready 🚀</h2>
52
+ <p style="color: #64748b; line-height: 1.6;">Your Payment was successful. Below are your license details and default login credentials to access the monitor dashboard.</p>
53
+
54
+ <!-- License Card -->
55
+ <div style="background-color: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: center;">
56
+ <p style="color: #94a3b8; margin: 0 0 10px 0; text-transform: uppercase; letter-spacing: 1.5px; font-size: 12px; font-weight: 600;">License Key</p>
57
+ <div style="color: #4f46e5; font-size: 24px; font-weight: 700; letter-spacing: 1px; font-family: monospace; background: #fff; padding: 10px; border-radius: 8px; display: inline-block;">
58
+ ${licenseKey}
59
+ </div>
60
+ <p style="margin-top: 15px; margin-bottom: 0; font-size: 14px; color: #64748b;">
61
+ <span style="color: #10b981;">● Active</span> &nbsp;|&nbsp; Valid until <strong>${dateStr}</strong>
62
+ </p>
63
+ </div>
64
+
65
+ <!-- Credentials Card -->
66
+ <div style="background-color: #1e293b; color: #ffffff; border-radius: 12px; padding: 25px; margin-bottom: 30px;">
67
+ <h3 style="margin: 0 0 15px 0; color: #38bdf8; font-size: 16px; border-bottom: 1px solid #334155; padding-bottom: 10px;">Default Admin Credentials</h3>
68
+ <table width="100%" cellpadding="0" cellspacing="0" border="0">
69
+ <tr>
70
+ <td style="padding: 8px 0; color: #94a3b8; width: 100px;">Username:</td>
71
+ <td style="padding: 8px 0; font-weight: 600; font-family: monospace; color: #ffffff;">admin@monitor.com</td>
72
+ </tr>
73
+ <tr>
74
+ <td style="padding: 8px 0; color: #94a3b8;">Password:</td>
75
+ <td style="padding: 8px 0; font-weight: 600; font-family: monospace; color: #ffffff;">AdminPass123!</td>
76
+ </tr>
77
+ </table>
78
+ <div style="margin-top: 15px; padding-top: 15px; border-top: 1px dotted #334155; color: #facc15; font-size: 13px;">
79
+ ⚠️ <strong>Important:</strong> Please change your password immediately upon first login for security.
80
+ </div>
81
+ </div>
82
+
83
+ <p style="color: #64748b; font-size: 14px; line-height: 1.6;">
84
+ To activate, enter the license key in your monitor dashboard at port <strong>3014</strong>.
85
+ </p>
86
+
87
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin-top: 30px;">
88
+ <tr>
89
+ <td align="center" style="border-radius: 8px;" bgcolor="#4f46e5">
90
+ <a href="#" style="font-size: 16px; font-family: Helvetica, Arial, sans-serif; color: #ffffff; text-decoration: none; border-radius: 8px; padding: 12px 24px; border: 1px solid #4f46e5; display: inline-block; font-weight: bold;">Open Dashboard</a>
91
+ </td>
92
+ </tr>
93
+ </table>
94
+ </td>
95
+ </tr>
96
+
97
+ <!-- Footer -->
98
+ <tr>
99
+ <td align="center" style="background-color: #f1f5f9; padding: 20px; color: #94a3b8; font-size: 12px;">
100
+ &copy; ${new Date().getFullYear()} MulaSoft. All rights reserved.<br>
101
+ This is an automated system email.
102
+ </td>
103
+ </tr>
104
+ </table>
105
+ </td>
106
+ </tr>
107
+ </table>
108
+ </body>
109
+ </html>
110
+ `;
111
+
112
+ try {
113
+ const info = await transporter.sendMail({
114
+ from: process.env.MAIL_FROM,
115
+ to: to,
116
+ subject: subject,
117
+ html: html
118
+ });
119
+ console.log(`Email sent: ${info.messageId}`);
120
+ } catch (error) {
121
+ console.error('Error sending email:', error);
122
+ // We don't throw here to avoid failing the response to user, just log it.
123
+ }
124
+ };
125
+
126
+ module.exports = { sendLicenseEmail };
@@ -214,7 +214,7 @@
214
214
  </div>
215
215
 
216
216
  <p style="text-align: center; color: var(--text-muted); margin-top: 20px;">
217
- Once installed, visit your server IP on port 3000 to activate.
217
+ Once installed, visit your server IP on port 3014 to activate.
218
218
  </p>
219
219
  </div>
220
220
 
@@ -240,12 +240,40 @@
240
240
  </div>
241
241
  </div>
242
242
 
243
+ <style>
244
+ .loader-spinner {
245
+ border: 2px solid rgba(255, 255, 255, 0.3);
246
+ border-top: 2px solid #fff;
247
+ border-radius: 50%;
248
+ width: 16px;
249
+ height: 16px;
250
+ animation: spin 1s linear infinite;
251
+ display: inline-block;
252
+ vertical-align: middle;
253
+ margin-right: 8px;
254
+ }
255
+
256
+ @keyframes spin {
257
+ 0% {
258
+ transform: rotate(0deg);
259
+ }
260
+
261
+ 100% {
262
+ transform: rotate(360deg);
263
+ }
264
+ }
265
+ </style>
266
+
243
267
  <script>
244
268
  const cashfree = Cashfree({
245
269
  mode: "production"
246
270
  });
247
271
 
272
+ let isSubmitting = false;
273
+
248
274
  document.getElementById('buyBtn').addEventListener('click', async () => {
275
+ if (isSubmitting) return;
276
+
249
277
  const email = document.getElementById('email').value;
250
278
  const phone = document.getElementById('phone').value;
251
279
  const btn = document.getElementById('buyBtn');
@@ -255,7 +283,8 @@
255
283
  return;
256
284
  }
257
285
 
258
- btn.innerText = 'Processing...';
286
+ isSubmitting = true;
287
+ btn.innerHTML = '<span class="loader-spinner"></span> Processing...';
259
288
  btn.disabled = true;
260
289
 
261
290
  try {
@@ -265,24 +294,42 @@
265
294
  headers: { 'Content-Type': 'application/json' },
266
295
  body: JSON.stringify({ email, phone })
267
296
  });
297
+
268
298
  const data = await res.json();
269
299
 
270
- if (data.payment_session_id) {
300
+ if (res.ok && data.payment_session_id) {
271
301
  // Redirect to Payment
302
+ const emailVal = document.getElementById('email').value;
303
+ const phoneVal = document.getElementById('phone').value;
304
+
272
305
  cashfree.checkout({
273
- paymentSessionId: data.payment_session_id,
274
- redirectTarget: "_self"
275
- // returnUrl: `${window.location.origin}/verify?order_id=${data.order_id}` // Handled by Cashfree self redirect if desired, but better to use returnUrl
306
+ paymentSessionId: data.payment_session_id
307
+ }).then(() => {
308
+ console.log("Checkout initiated");
309
+ });
310
+
311
+ cashfree.on("payment.success", (data) => {
312
+ window.location.href = `/verify?order_id=${data.orderId}&email=${encodeURIComponent(emailVal)}&phone=${encodeURIComponent(phoneVal)}`;
313
+ });
314
+
315
+ cashfree.on("payment.failed", (data) => {
316
+ alert("Payment Failed: " + data.message);
317
+ isSubmitting = false;
318
+ btn.innerHTML = 'Buy License Now';
319
+ btn.disabled = false;
276
320
  });
277
321
  } else {
278
- alert('Error creating order');
279
- btn.innerText = 'Buy License Now';
322
+ console.error('Order Error:', data);
323
+ alert('Error creating order: ' + (data.message || data.error || 'Unknown error'));
324
+ isSubmitting = false;
325
+ btn.innerHTML = 'Buy License Now';
280
326
  btn.disabled = false;
281
327
  }
282
328
  } catch (e) {
283
329
  console.error(e);
284
330
  alert('Connection Error');
285
- btn.innerText = 'Buy License Now';
331
+ isSubmitting = false;
332
+ btn.innerHTML = 'Buy License Now';
286
333
  btn.disabled = false;
287
334
  }
288
335
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "krsyer-server-monitor-pro",
3
- "version": "1.0.11",
3
+ "version": "1.0.13",
4
4
  "description": "API to get server details like IP address, health, applications running, etc.",
5
5
  "main": "lib/server.js",
6
6
  "bin": {