node-paytmpg 5.3.2 → 6.4.2

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/example.js CHANGED
@@ -1,51 +1,34 @@
1
- var express=require('express')
2
- var app=express()
3
-
4
- var MONGOURL="mongodb+srv://username:pasws@host.net/dbname";
5
-
6
- /***
7
- * Uncomment in case you want to use multidborm
8
- * https://www.npmjs.com/package/multi-db-orm
9
-
10
- const { MultiDbORM, FireStoreDB, MongoDB, SQLiteDB, Sync } = require("multi-db-orm");
11
- var mongodb = new MongoDB(MONGOURL);
12
- app.multidborm = mongodb;
13
-
14
- */
15
-
16
- app.set('np_config', {
17
- "host_url":"http://127.0.0.1:5542",
18
- "view_path":"/../views/",
19
- "paytm_url":"https://securegw-stage.paytm.in",
20
- "MID":"XXXXX",
21
- "WEBSITE":"WEBSTAGING",
22
- "KEY":"XXXXX",
23
- "CHANNEL_ID":"WAP",
24
- "INDUSTRY_TYPE_ID":"Retail",
25
- "homepage":"/",
26
- "path_prefix":"_pay",
27
- "theme_color":"#231530",
28
- "db_url":MONGOURL // Remove this property in case you want to use multidborm
1
+ const express = require('express');
2
+ const { SQLiteDB } = require('multi-db-orm');
3
+ const { createPaymentMiddleware } = require('./index');
4
+
5
+ try { require('dotenv').config(); } catch (e) { }
6
+
7
+ const app = express();
8
+
9
+ // Local SQLite sample DB via multi-db-orm; swap for your own adapter
10
+ const db = new SQLiteDB('test.db');
11
+
12
+ const payment = createPaymentMiddleware({
13
+ host_url: process.env.NP_HOST_URL || 'http://localhost:5543',
14
+ path_prefix: process.env.NP_PATH_PREFIX || '_pay',
15
+ homepage: '/',
16
+ payu_url: 'https://secure.payu.in', // use https://test.payu.in for sandbox
17
+ MID: process.env.NP_MID || '12345',
18
+ WEBSITE: process.env.NP_WEBSITE || 'WEBSTAGING',
19
+ KEY: process.env.NP_KEY || 'abcdef',
20
+ SECRET: process.env.NP_SECRET || 'abcdef', // salt for payu / razor
21
+ CHANNEL_ID: process.env.NP_CHANNEL_ID || 'WAP',
22
+ INDUSTRY_TYPE_ID: process.env.NP_INDUSTRY_TYPE_ID || 'Retail',
23
+ theme: {
24
+ primary: '#231530',
25
+ accent: '#5ce1e6',
26
+ },
27
+ brand: 'DemoPay',
28
+ }, db);
29
+
30
+ app.use(payment);
31
+
32
+ app.listen(process.env.PORT || 5543, function () {
33
+ console.log('Server started at', process.env.PORT || 5543);
29
34
  });
30
-
31
- if(process.env.CONFIG){
32
-
33
- app.set('np_config', JSON.parse(process.env.CONFIG));
34
- console.log('using config from env',process.env.CONFIG);
35
-
36
- }
37
-
38
- require('./index')(app,express)
39
-
40
- app.all('/',function(req,res)
41
- {
42
- res.redirect('/_pay/init')
43
- })
44
-
45
-
46
- app.listen(process.env.PORT || 5542,function()
47
- {
48
-
49
- console.log("Server Started At ",process.env.PORT || 5542)
50
-
51
- })
package/index.js CHANGED
@@ -1,23 +1,83 @@
1
- module.exports = (app, express, callbacks) => {
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const bodyParser = require('body-parser');
4
+ const exphbs = require('express-handlebars');
5
+ const mongoose = require('mongoose');
2
6
 
3
- var module = {};
7
+ const buildConfig = require('./lib/config/buildConfig');
4
8
 
5
- if (app && express) {
6
- require('./app/routes/payment_route.js')(app, express, callbacks)
7
- var config = (app.get('np_config'))
8
- if (config.db_url) {
9
+ /**
10
+ * Creates an isolated payment middleware that can be mounted on any Express app.
11
+ * This keeps the payment creation/verification logic intact while exposing a cleaner API.
12
+ *
13
+ * @param {object} userConfig - configuration overrides (gateway keys, branding, hooks, etc.)
14
+ * @param {object} db - optional multi-db-orm instance; if omitted and db_url is provided, MongoDB is used
15
+ * @returns {import('express').Application} configured sub-application ready to mount
16
+ */
17
+ function createPaymentMiddleware(userConfig = {}, db) {
18
+ const config = buildConfig(userConfig);
19
+ const subApp = express();
9
20
 
10
- module.Transaction = require('./app/models/np_transaction.model.js')
11
- module.User = require('./app/models/np_user.model.js')
21
+ // expose config + optional db handle for downstream controllers
22
+ subApp.set('np_config', config);
23
+ subApp.locals.theme = config.theme || {};
24
+ subApp.locals.brand = config.brand || 'Secure Pay';
25
+ subApp.locals.logo = config.logo;
26
+ subApp.locals.themeName = config.themeName || (config.theme && config.theme.name) || 'dark';
27
+ if (config.db_url) {
28
+ mongoose.Promise = global.Promise;
29
+ mongoose.connect(config.db_url, {
30
+ useUnifiedTopology: true,
31
+ useNewUrlParser: true,
32
+ }).then(() => {
33
+ console.log('PaytmPG : Connected to MongoDB');
34
+ }).catch(err => {
35
+ console.log('PaytmPG : Failed to connect MongoDB', err);
36
+ });
37
+ } else if (db) {
38
+ subApp.multidborm = db;
39
+ }
12
40
 
13
- } else if (app.multidborm) {
41
+ // view engine + theming
42
+ const viewRoot = config.templateDir
43
+ ? path.resolve(config.templateDir)
44
+ : path.join(__dirname, 'app', 'views');
45
+ const layoutPath = path.join(viewRoot, 'layouts', 'index.hbs');
14
46
 
15
- module.Transaction = app.NPTransaction;
16
- module.User = app.NPUser;
47
+ subApp.engine('hbs', exphbs({
48
+ extname: 'hbs',
49
+ defaultLayout: layoutPath,
50
+ helpers: {
51
+ theme_color: () => config.theme_color,
52
+ logo: () => config.logo,
53
+ brand: () => config.brand || 'Secure Pay',
54
+ },
55
+ }));
56
+ subApp.set('view engine', 'handlebars');
17
57
 
18
- }
19
- }
58
+ // body parsing with raw body capture (needed for webhooks)
59
+ const saveRawBody = (req, res, buf) => {
60
+ req.rawBody = buf.toString();
61
+ };
62
+ subApp.use(bodyParser.urlencoded({ extended: true }));
63
+ subApp.use(bodyParser.json({ verify: saveRawBody }));
64
+ // wire routes against existing payment controller (logic unchanged)
65
+ const callbacks = config.callbacks || userConfig.callbacks;
66
+ const pc = require('./app/controllers/payment_controller')(subApp, callbacks);
67
+
68
+ subApp.all('/init', pc.init);
69
+ subApp.all('/callback', pc.callback);
70
+ subApp.all('/api/webhook', pc.webhook);
71
+ subApp.all('/api/status', pc.status);
72
+ subApp.all('/api/createTxn/token', pc.createTxnToken);
73
+ subApp.all('/api/createTxn', pc.createTxn);
74
+ subApp.all('/', pc.init);
75
+
76
+ subApp.use(express.static(path.join(__dirname, 'public')), pc.init);
77
+
78
+ return subApp;
79
+ }
80
+
81
+ module.exports = { createPaymentMiddleware };
20
82
 
21
- return module;
22
- };
23
83
 
@@ -0,0 +1,113 @@
1
+ const defaults = require('./defaults');
2
+ const { validateConfig } = require('./validator');
3
+
4
+ function pickEnv(keys) {
5
+ const output = {};
6
+ keys.forEach((key) => {
7
+ if (process.env[key] !== undefined) {
8
+ output[key] = process.env[key];
9
+ }
10
+ });
11
+ return output;
12
+ }
13
+
14
+ function buildConfig(userConfig = {}) {
15
+ const envOverrides = pickEnv([
16
+ 'NP_HOST_URL',
17
+ 'NP_PATH_PREFIX',
18
+ 'NP_HOMEPAGE',
19
+ 'NP_TEMPLATE_DIR',
20
+ 'NP_THEME_COLOR',
21
+ 'NP_LOGO',
22
+ 'NP_DB_URL',
23
+ 'NP_PAYTM_URL',
24
+ 'NP_RAZOR_URL',
25
+ 'NP_PAYU_URL',
26
+ 'NP_OPEN_MONEY_URL',
27
+ 'NP_MID',
28
+ 'NP_WEBSITE',
29
+ 'NP_KEY',
30
+ 'NP_SECRET',
31
+ 'NP_CHANNEL_ID',
32
+ 'NP_INDUSTRY_TYPE_ID',
33
+ 'NP_MODE',
34
+ 'NP_THEME_NAME',
35
+ ]);
36
+
37
+ const merged = {
38
+ ...defaults,
39
+ ...normalizeEnv(envOverrides),
40
+ ...userConfig,
41
+ };
42
+
43
+ // theme normalization
44
+ const themeName = (userConfig.themeName || userConfig.theme?.name || merged.theme_name || 'dark').toLowerCase();
45
+ const theme = {
46
+ primary: getThemeColor(userConfig, merged),
47
+ accent: userConfig?.theme?.accent || merged.theme_accent || '#4ae0ff',
48
+ surface: userConfig?.theme?.surface || '#0f1021',
49
+ text: userConfig?.theme?.text || '#e9ecf2',
50
+ success: userConfig?.theme?.success || '#24cf5f',
51
+ danger: userConfig?.theme?.danger || '#ff6b6b',
52
+ name: themeName,
53
+ };
54
+
55
+ merged.theme = theme;
56
+ merged.theme_color = theme.primary;
57
+ merged.logo = userConfig.logo || merged.logo;
58
+ merged.brand = userConfig.brand || 'Secure Pay';
59
+ merged.callbacks = userConfig.callbacks || userConfig.hooks || merged.callbacks || {};
60
+ merged.templateDir = userConfig.templateDir || merged.templateDir;
61
+ merged.themeName = themeName;
62
+
63
+ // ensure view path remains compatible with legacy controllers
64
+ if (!merged.view_path) {
65
+ merged.view_path = '/../views/';
66
+ }
67
+
68
+ if (userConfig.host_url) {
69
+ merged.host_url = userConfig.host_url;
70
+ } else if (process.env.NP_HOST_URL) {
71
+ merged.host_url = process.env.NP_HOST_URL;
72
+ }
73
+
74
+ return validateConfig(merged);
75
+ }
76
+
77
+ function getThemeColor(userConfig, merged) {
78
+ if (userConfig?.theme?.primary) return userConfig.theme.primary;
79
+ if (userConfig.theme_color) return userConfig.theme_color;
80
+ if (merged.NP_THEME_COLOR) return merged.NP_THEME_COLOR;
81
+ return merged.theme_color;
82
+ }
83
+
84
+ function normalizeEnv(env) {
85
+ const mapping = {
86
+ NP_HOST_URL: 'host_url',
87
+ NP_PATH_PREFIX: 'path_prefix',
88
+ NP_HOMEPAGE: 'homepage',
89
+ NP_TEMPLATE_DIR: 'templateDir',
90
+ NP_THEME_COLOR: 'theme_color',
91
+ NP_LOGO: 'logo',
92
+ NP_DB_URL: 'db_url',
93
+ NP_PAYTM_URL: 'paytm_url',
94
+ NP_RAZOR_URL: 'razor_url',
95
+ NP_PAYU_URL: 'payu_url',
96
+ NP_OPEN_MONEY_URL: 'open_money_url',
97
+ NP_MID: 'MID',
98
+ NP_WEBSITE: 'WEBSITE',
99
+ NP_KEY: 'KEY',
100
+ NP_SECRET: 'SECRET',
101
+ NP_CHANNEL_ID: 'CHANNEL_ID',
102
+ NP_INDUSTRY_TYPE_ID: 'INDUSTRY_TYPE_ID',
103
+ NP_MODE: 'mode',
104
+ NP_THEME_NAME: 'themeName',
105
+ };
106
+ return Object.keys(env).reduce((acc, key) => {
107
+ const target = mapping[key];
108
+ if (target) acc[target] = env[key];
109
+ return acc;
110
+ }, {});
111
+ }
112
+
113
+ module.exports = buildConfig;
@@ -0,0 +1,37 @@
1
+ module.exports = {
2
+ // Server configuration
3
+ host_url: 'http://localhost:3000',
4
+ path_prefix: '_pay',
5
+ homepage: '/',
6
+
7
+ // Template configuration
8
+ templateDir: null, // null = use built-in views, or provide path to custom templates
9
+ templateEngine: 'handlebars',
10
+
11
+ // UI Customization
12
+ theme_color: '#3399cc',
13
+ logo: '/favicon.ico',
14
+
15
+ // Transaction ID configuration
16
+ id_length: 10,
17
+
18
+ // Database
19
+ db_url: null, // MongoDB URL (legacy), leave null to use multidborm
20
+
21
+ // Payment Gateway URLs
22
+ // paytm_url: null, // e.g., 'https://securegw-stage.paytm.in' for test, 'https://securegw.paytm.in' for production
23
+ // razor_url: null, // e.g., 'https://api.razorpay.com/v1/'
24
+ // payu_url: null, // e.g., 'https://test.payu.in' for test, 'https://secure.payu.in' for production
25
+ // open_money_url: null, // e.g., 'https://sandbox-icp-api.bankopen.co/api' for sandbox, 'https://icp-api.bankopen.co/api' for live
26
+
27
+ // Gateway Credentials (must be provided by user)
28
+ // MID: null,
29
+ // WEBSITE: null,
30
+ // KEY: null,
31
+ // SECRET: null,
32
+ // CHANNEL_ID: 'WEB',
33
+ // INDUSTRY_TYPE_ID: 'Retail',
34
+
35
+ // Payment mode configuration (optional)
36
+ // mode: null // JSON string of enabled payment modes for Paytm
37
+ };
@@ -0,0 +1,103 @@
1
+ const defaults = require('./defaults');
2
+
3
+ /**
4
+ * Validates and merges config with defaults
5
+ * @param {Object} userConfig - User-provided configuration
6
+ * @returns {Object} Merged and validated configuration
7
+ * @throws {Error} If required fields are missing
8
+ */
9
+ function validateConfig(userConfig) {
10
+ if (!userConfig || typeof userConfig !== 'object') {
11
+ throw new Error('Config must be an object');
12
+ }
13
+
14
+ // Merge with defaults
15
+ const config = { ...defaults, ...userConfig };
16
+
17
+ // Detect which payment gateway is being used
18
+ const hasPaytm = !!config.paytm_url;
19
+ const hasRazorpay = !!config.razor_url;
20
+ const hasPayU = !!config.payu_url;
21
+ const hasOpenMoney = !!config.open_money_url;
22
+
23
+ if (!hasPaytm && !hasRazorpay && !hasPayU && !hasOpenMoney) {
24
+ throw new Error(
25
+ 'At least one payment gateway must be configured. ' +
26
+ 'Please provide one of: paytm_url, razor_url, payu_url, or open_money_url'
27
+ );
28
+ }
29
+
30
+ // Validate required credentials based on gateway
31
+ if (hasPaytm) {
32
+ validatePaytmConfig(config);
33
+ }
34
+
35
+ if (hasRazorpay) {
36
+ validateRazorpayConfig(config);
37
+ }
38
+
39
+ if (hasPayU) {
40
+ validatePayUConfig(config);
41
+ }
42
+
43
+ if (hasOpenMoney) {
44
+ validateOpenMoneyConfig(config);
45
+ }
46
+
47
+ // Validate common fields
48
+ if (!config.host_url) {
49
+ throw new Error('host_url is required');
50
+ }
51
+
52
+ if (!config.path_prefix) {
53
+ throw new Error('path_prefix is required');
54
+ }
55
+
56
+ return config;
57
+ }
58
+
59
+ function validatePaytmConfig(config) {
60
+ const required = ['MID', 'WEBSITE', 'KEY', 'CHANNEL_ID', 'INDUSTRY_TYPE_ID'];
61
+ const missing = required.filter(field => !config[field]);
62
+
63
+ if (missing.length > 0) {
64
+ throw new Error(
65
+ `Paytm configuration incomplete. Missing fields: ${missing.join(', ')}`
66
+ );
67
+ }
68
+ }
69
+
70
+ function validateRazorpayConfig(config) {
71
+ const required = ['KEY', 'SECRET'];
72
+ const missing = required.filter(field => !config[field]);
73
+
74
+ if (missing.length > 0) {
75
+ throw new Error(
76
+ `Razorpay configuration incomplete. Missing fields: ${missing.join(', ')}`
77
+ );
78
+ }
79
+ }
80
+
81
+ function validatePayUConfig(config) {
82
+ const required = ['KEY', 'SECRET'];
83
+ const missing = required.filter(field => !config[field]);
84
+
85
+ if (missing.length > 0) {
86
+ throw new Error(
87
+ `PayU configuration incomplete. Missing fields: ${missing.join(', ')}`
88
+ );
89
+ }
90
+ }
91
+
92
+ function validateOpenMoneyConfig(config) {
93
+ const required = ['KEY', 'SECRET'];
94
+ const missing = required.filter(field => !config[field]);
95
+
96
+ if (missing.length > 0) {
97
+ throw new Error(
98
+ `OpenMoney configuration incomplete. Missing fields: ${missing.join(', ')}`
99
+ );
100
+ }
101
+ }
102
+
103
+ module.exports = { validateConfig };
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Database Service
3
+ * Abstracts database operations to support both MongoDB (legacy) and MultiDB ORM
4
+ */
5
+ class DatabaseService {
6
+ constructor(db, config) {
7
+ this.db = db;
8
+ this.config = config;
9
+ this.usingMultiDbOrm = this.detectDbType();
10
+
11
+ // Will be initialized later
12
+ this.Transaction = null;
13
+ this.User = null;
14
+ }
15
+
16
+ /**
17
+ * Detects whether we're using MultiDB ORM or MongoDB
18
+ */
19
+ detectDbType() {
20
+ // Using MultiDB ORM if db instance provided and no db_url in config
21
+ return !!this.db && !this.config.db_url;
22
+ }
23
+
24
+ /**
25
+ * Initializes transaction model
26
+ */
27
+ initTransactionModel() {
28
+ if (this.config.db_url) {
29
+ // Legacy MongoDB mode
30
+ this.Transaction = require('../models/transaction.model');
31
+ } else if (this.db) {
32
+ // MultiDB ORM mode
33
+ const sample = {
34
+ orderId: "string",
35
+ cusId: "string",
36
+ time: 1770051201752,
37
+ timeStamp: 1770051201752,
38
+ status: "string",
39
+ name: "string",
40
+ email: "string",
41
+ phone: "string",
42
+ amount: 1,
43
+ pname: "string",
44
+ extra: "stringlarge",
45
+ TXNID: "27118670199",
46
+ returnUrl: "string"
47
+ };
48
+
49
+ const multiDbPlugin = require('../models/np_multidbplugin');
50
+ this.Transaction = multiDbPlugin('nptransactions', this.db, sample);
51
+ this.Transaction.db = this.db;
52
+ this.Transaction.modelname = 'nptransactions';
53
+ this.Transaction.idFieldName = 'orderId';
54
+ }
55
+
56
+ return this.Transaction;
57
+ }
58
+
59
+ /**
60
+ * Initializes user model
61
+ */
62
+ initUserModel() {
63
+ if (this.config.db_url) {
64
+ // Legacy MongoDB mode
65
+ this.User = require('../models/user.model');
66
+ } else if (this.db) {
67
+ // MultiDB ORM mode
68
+ const sample = {
69
+ id: "string",
70
+ name: "string",
71
+ email: "string",
72
+ phone: "string"
73
+ };
74
+
75
+ const multiDbPlugin = require('../models/np_multidbplugin');
76
+ this.User = multiDbPlugin('npusers', this.db, sample);
77
+ this.User.db = this.db;
78
+ this.User.modelname = 'npusers';
79
+ this.User.idFieldName = 'id';
80
+ }
81
+
82
+ return this.User;
83
+ }
84
+
85
+ /**
86
+ * Finds one document
87
+ * @param {Object} model - Mongoose model or MultiDB model
88
+ * @param {Object} query - Query object
89
+ * @param {Function} callback - Callback function (err, doc)
90
+ */
91
+ findOne(model, query, callback) {
92
+ if (this.usingMultiDbOrm) {
93
+ model.findOne(query, callback, model);
94
+ } else {
95
+ model.findOne(query, callback);
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Creates and saves a new document
101
+ * @param {Object} modelClass - Model constructor
102
+ * @param {Object} data - Data to save
103
+ * @returns {Promise} Promise that resolves with saved document
104
+ */
105
+ create(modelClass, data) {
106
+ const doc = new modelClass(data);
107
+ return doc.save();
108
+ }
109
+
110
+ /**
111
+ * Updates one document
112
+ * @param {Object} model - Mongoose model or MultiDB model
113
+ * @param {Object} query - Query object
114
+ * @param {Object} update - Update object
115
+ * @param {Function} callback - Callback function (err, result)
116
+ */
117
+ updateOne(model, query, update, callback) {
118
+ if (this.usingMultiDbOrm) {
119
+ model.updateOne(query, update, callback);
120
+ } else {
121
+ model.updateOne(query, update, callback);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Gets the Transaction model
127
+ */
128
+ getTransactionModel() {
129
+ if (!this.Transaction) {
130
+ this.initTransactionModel();
131
+ }
132
+ return this.Transaction;
133
+ }
134
+
135
+ /**
136
+ * Gets the User model
137
+ */
138
+ getUserModel() {
139
+ if (!this.User) {
140
+ this.initUserModel();
141
+ }
142
+ return this.User;
143
+ }
144
+
145
+ /**
146
+ * Returns whether using MultiDB ORM
147
+ */
148
+ isUsingMultiDbOrm() {
149
+ return this.usingMultiDbOrm;
150
+ }
151
+ }
152
+
153
+ module.exports = DatabaseService;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Generates a random alphanumeric ID
3
+ * @param {number} length - Length of the ID to generate
4
+ * @returns {string} Random ID
5
+ */
6
+ function generateId(length = 10) {
7
+ const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
8
+ let text = "";
9
+
10
+ for (let i = 0; i < length; i++) {
11
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
12
+ }
13
+
14
+ return text;
15
+ }
16
+
17
+ /**
18
+ * Generates an order ID with a prefix
19
+ * @param {string} prefix - Prefix for the order ID (e.g., 'pay_', 'payu_')
20
+ * @param {number} length - Length of the random part
21
+ * @returns {string} Order ID
22
+ */
23
+ function generateOrderId(prefix = '', length = 10) {
24
+ return prefix + generateId(length);
25
+ }
26
+
27
+ module.exports = {
28
+ generateId,
29
+ generateOrderId
30
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Sanitizes request body by parsing amount fields to floats
3
+ * @param {Object} body - Request body to sanitize
4
+ * @returns {Object} Sanitized request body
5
+ */
6
+ function sanitizeRequest(body) {
7
+ if (!body || typeof body !== 'object') {
8
+ return body;
9
+ }
10
+
11
+ // Parse amount to float if present
12
+ if (body.amount) {
13
+ body.amount = parseFloat(body.amount);
14
+ }
15
+
16
+ if (body.TXN_AMOUNT) {
17
+ body.TXN_AMOUNT = parseFloat(body.TXN_AMOUNT);
18
+ }
19
+
20
+ return body;
21
+ }
22
+
23
+ module.exports = {
24
+ sanitizeRequest
25
+ };