node-paytmpg 6.4.6 → 7.0.1

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.
Files changed (58) hide show
  1. package/README.MD +132 -182
  2. package/app/views/layouts/index.hbs +7 -7
  3. package/app/views/result.hbs +1 -1
  4. package/dist/app/controllers/adapters/open_money.js +400 -0
  5. package/dist/app/controllers/adapters/paytm.js +34 -0
  6. package/{app → dist/app}/controllers/adapters/payu.js +208 -239
  7. package/dist/app/controllers/checksum/PaytmChecksum.js +118 -0
  8. package/dist/app/controllers/checksum/checksum.js +158 -0
  9. package/dist/app/controllers/checksum/crypt.js +117 -0
  10. package/dist/app/controllers/checksum/server.js +130 -0
  11. package/dist/app/controllers/payment.controller.js +985 -0
  12. package/dist/app/controllers/static/loadingsvg.js +54 -0
  13. package/dist/app/controllers/user.controller.js +53 -0
  14. package/dist/app/models/index.js +2 -0
  15. package/dist/app/routes/payment_route.js +46 -0
  16. package/dist/app/utils/buildConfig.js +210 -0
  17. package/dist/app/utils/utils.js +20 -0
  18. package/dist/app/views/home.hbs +22 -0
  19. package/dist/app/views/init.hbs +98 -0
  20. package/dist/app/views/layouts/index.hbs +53 -0
  21. package/dist/app/views/result.hbs +33 -0
  22. package/dist/index.js +119 -0
  23. package/dist/package.json +67 -0
  24. package/dist/public/css/style.css +455 -0
  25. package/dist/public/js/index.js +283 -0
  26. package/dist/public/layer_checkout.js +38 -0
  27. package/dist/public/pay.png +0 -0
  28. package/dist/public/start.png +0 -0
  29. package/dist/public/start2.png +0 -0
  30. package/dist/public/stat.png +0 -0
  31. package/dist/public/test.html +24 -0
  32. package/dist/public/test.html~ +24 -0
  33. package/package.json +29 -6
  34. package/public/test.html~ +24 -0
  35. package/.github/workflows/codeql-analysis.yml +0 -71
  36. package/.github/workflows/nodejs.yml +0 -24
  37. package/.github/workflows/npm-publish.yml +0 -23
  38. package/Dockerfile +0 -9
  39. package/app/controllers/adapters/open_money.js +0 -515
  40. package/app/controllers/checksum/PaytmChecksum.js +0 -94
  41. package/app/controllers/checksum/checksum.js +0 -154
  42. package/app/controllers/checksum/crypt.js +0 -98
  43. package/app/controllers/checksum/server.js +0 -132
  44. package/app/controllers/np_user.controller.js +0 -89
  45. package/app/controllers/payment_controller.js +0 -1204
  46. package/app/models/np_multidbplugin.js +0 -111
  47. package/app/models/np_transaction.model.js +0 -16
  48. package/app/models/np_user.model.js +0 -12
  49. package/app/routes/payment_route.js +0 -73
  50. package/app.yaml +0 -18
  51. package/example.js +0 -34
  52. package/index.js +0 -86
  53. package/lib/config/buildConfig.js +0 -113
  54. package/lib/config/defaults.js +0 -37
  55. package/lib/config/validator.js +0 -103
  56. package/lib/services/database.service.js +0 -153
  57. package/lib/utils/id-generator.js +0 -30
  58. package/lib/utils/sanitizer.js +0 -25
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LoadingSVG = void 0;
4
+ exports.LoadingSVG = ` <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin:auto;background:#fff;display:block;" width="200px" height="200px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
5
+ <g transform="rotate(0 50 50)">
6
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
7
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.9166666666666666s" repeatCount="indefinite"></animate>
8
+ </rect>
9
+ </g><g transform="rotate(30 50 50)">
10
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
11
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.8333333333333334s" repeatCount="indefinite"></animate>
12
+ </rect>
13
+ </g><g transform="rotate(60 50 50)">
14
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
15
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.75s" repeatCount="indefinite"></animate>
16
+ </rect>
17
+ </g><g transform="rotate(90 50 50)">
18
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
19
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.6666666666666666s" repeatCount="indefinite"></animate>
20
+ </rect>
21
+ </g><g transform="rotate(120 50 50)">
22
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
23
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5833333333333334s" repeatCount="indefinite"></animate>
24
+ </rect>
25
+ </g><g transform="rotate(150 50 50)">
26
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
27
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.5s" repeatCount="indefinite"></animate>
28
+ </rect>
29
+ </g><g transform="rotate(180 50 50)">
30
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
31
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.4166666666666667s" repeatCount="indefinite"></animate>
32
+ </rect>
33
+ </g><g transform="rotate(210 50 50)">
34
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
35
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.3333333333333333s" repeatCount="indefinite"></animate>
36
+ </rect>
37
+ </g><g transform="rotate(240 50 50)">
38
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
39
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.25s" repeatCount="indefinite"></animate>
40
+ </rect>
41
+ </g><g transform="rotate(270 50 50)">
42
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
43
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.16666666666666666s" repeatCount="indefinite"></animate>
44
+ </rect>
45
+ </g><g transform="rotate(300 50 50)">
46
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
47
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="-0.08333333333333333s" repeatCount="indefinite"></animate>
48
+ </rect>
49
+ </g><g transform="rotate(330 50 50)">
50
+ <rect x="47" y="24" rx="3" ry="6" width="6" height="12" fill="#0097a7">
51
+ <animate attributeName="opacity" values="1;0" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite"></animate>
52
+ </rect>
53
+ </g>
54
+ </svg>`;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.NPUserController = void 0;
4
+ const utils_1 = require("../utils/utils");
5
+ class NPUserController {
6
+ constructor(db, tableName = 'npusers') {
7
+ this.db = db;
8
+ this.tableName = tableName;
9
+ }
10
+ async init() {
11
+ const sample = {
12
+ id: "user_aB3dE9xY1Z",
13
+ name: "tset",
14
+ email: "testgmailcom",
15
+ phone: "12345678",
16
+ };
17
+ this.db.create(this.tableName, sample);
18
+ }
19
+ async create(userData) {
20
+ try {
21
+ let user = await this.db.getOne(this.tableName, { email: userData.email });
22
+ if (user) {
23
+ const myquery = { email: userData.email };
24
+ const objForUpdate = Object.assign({}, user);
25
+ if (userData.email && userData.email.indexOf("@") !== -1)
26
+ objForUpdate.email = userData.email;
27
+ if (userData.phone && userData.phone.length > 2)
28
+ objForUpdate.phone = userData.phone;
29
+ if (userData.name && userData.name.length > 2)
30
+ objForUpdate.name = userData.name;
31
+ const newvalues = { $set: objForUpdate };
32
+ try {
33
+ await this.db.update(this.tableName, myquery, newvalues);
34
+ return objForUpdate;
35
+ }
36
+ catch (uErr) {
37
+ throw new Error(uErr.message || 'Some error occurred while updating users.');
38
+ }
39
+ }
40
+ else {
41
+ userData.id = "user_" + utils_1.Utils.makeid();
42
+ await this.db.insert(this.tableName, userData);
43
+ return userData;
44
+ }
45
+ }
46
+ catch (err) {
47
+ throw new Error(err.message || 'Some error occurred while creating users.');
48
+ }
49
+ }
50
+ ;
51
+ }
52
+ exports.NPUserController = NPUserController;
53
+ ;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
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 body_parser_1 = __importDefault(require("body-parser"));
7
+ const express_handlebars_1 = __importDefault(require("express-handlebars"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const payment_controller_1 = require("../controllers/payment.controller");
10
+ const paymentRoute = function (app, express, callbacks) {
11
+ const config = app.get('np_config');
12
+ const pc = new payment_controller_1.PaymentController(app, callbacks);
13
+ const router = express.Router();
14
+ app.set('view_path', __dirname + config.view_path);
15
+ const vp = app.get('view_path');
16
+ console.log('PaytmPG : Using MultiDB ORM');
17
+ app.engine('hbs', (0, express_handlebars_1.default)({
18
+ extname: 'hbs',
19
+ defaultLayout: vp + '/layouts/index.hbs',
20
+ helpers: {
21
+ theme_color: function () {
22
+ return config.theme_color;
23
+ },
24
+ logo: function () {
25
+ return config.logo;
26
+ }
27
+ }
28
+ }));
29
+ app.set('view engine', 'handlebars');
30
+ let saveRawBody = function (req, res, buf, encoding) {
31
+ req.rawBody = buf.toString();
32
+ };
33
+ app.use(body_parser_1.default.urlencoded({ extended: true }));
34
+ app.use(body_parser_1.default.json({ verify: saveRawBody }));
35
+ app.use('/' + config.path_prefix, express.static(path_1.default.join(__dirname, '../../public')));
36
+ app.use('/' + config.path_prefix, router);
37
+ router.all('/', pc.init);
38
+ router.all('/init', pc.init);
39
+ router.all('/callback', pc.callback);
40
+ router.all('/api/webhook', pc.webhook);
41
+ router.all('/api/status', pc.status);
42
+ router.all('/api/createTxn/token', pc.createTxnToken);
43
+ router.all('/api/createTxn', pc.createTxn);
44
+ return router;
45
+ };
46
+ exports.default = paymentRoute;
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildConfig = buildConfig;
4
+ const defaults = {
5
+ // Server configuration
6
+ host_url: 'http://localhost:3000',
7
+ path_prefix: '_pay',
8
+ homepage: '/',
9
+ // Template configuration
10
+ templateDir: null, // null = use built-in views, or provide path to custom templates
11
+ templateEngine: 'handlebars',
12
+ // UI Customization
13
+ theme_color: '#3399cc',
14
+ logo: '/favicon.ico',
15
+ // Transaction ID configuration
16
+ id_length: 10,
17
+ // Database
18
+ db_url: null, // MongoDB URL (legacy), leave null to use multidborm
19
+ // Payment Gateway URLs
20
+ // paytm_url: null, // e.g., 'https://securegw-stage.paytm.in' for test, 'https://securegw.paytm.in' for production
21
+ // razor_url: null, // e.g., 'https://api.razorpay.com/v1/'
22
+ // payu_url: null, // e.g., 'https://test.payu.in' for test, 'https://secure.payu.in' for production
23
+ // open_money_url: null, // e.g., 'https://sandbox-icp-api.bankopen.co/api' for sandbox, 'https://icp-api.bankopen.co/api' for live
24
+ // Gateway Credentials (must be provided by user)
25
+ // MID: null,
26
+ // WEBSITE: null,
27
+ // KEY: null,
28
+ // SECRET: null,
29
+ // CHANNEL_ID: 'WEB',
30
+ // INDUSTRY_TYPE_ID: 'Retail',
31
+ // Payment mode configuration (optional)
32
+ // mode: null // JSON string of enabled payment modes for Paytm
33
+ };
34
+ function pickEnv(keys) {
35
+ const output = {};
36
+ keys.forEach((key) => {
37
+ if (process.env[key] !== undefined) {
38
+ output[key] = process.env[key];
39
+ }
40
+ });
41
+ return output;
42
+ }
43
+ /**
44
+ * Validates and merges config with defaults
45
+ * @param {Object} userConfig - User-provided configuration
46
+ * @returns {Object} Merged and validated configuration
47
+ * @throws {Error} If required fields are missing
48
+ */
49
+ function validateConfig(userConfig) {
50
+ if (!userConfig || typeof userConfig !== 'object') {
51
+ throw new Error('Config must be an object');
52
+ }
53
+ // Merge with defaults
54
+ const config = { ...defaults, ...userConfig };
55
+ // Detect which payment gateway is being used
56
+ const hasPaytm = !!config.paytm_url;
57
+ const hasRazorpay = !!config.razor_url;
58
+ const hasPayU = !!config.payu_url;
59
+ const hasOpenMoney = !!config.open_money_url;
60
+ if (!hasPaytm && !hasRazorpay && !hasPayU && !hasOpenMoney) {
61
+ throw new Error('At least one payment gateway must be configured. ' +
62
+ 'Please provide one of: paytm_url, razor_url, payu_url, or open_money_url');
63
+ }
64
+ // Validate required credentials based on gateway
65
+ if (hasPaytm) {
66
+ validatePaytmConfig(config);
67
+ }
68
+ if (hasRazorpay) {
69
+ validateRazorpayConfig(config);
70
+ }
71
+ if (hasPayU) {
72
+ validatePayUConfig(config);
73
+ }
74
+ if (hasOpenMoney) {
75
+ validateOpenMoneyConfig(config);
76
+ }
77
+ // Validate common fields
78
+ if (!config.host_url) {
79
+ throw new Error('host_url is required');
80
+ }
81
+ if (!config.path_prefix) {
82
+ throw new Error('path_prefix is required');
83
+ }
84
+ return config;
85
+ }
86
+ function validatePaytmConfig(config) {
87
+ const required = ['MID', 'WEBSITE', 'KEY', 'CHANNEL_ID', 'INDUSTRY_TYPE_ID'];
88
+ const missing = required.filter(field => !config[field]);
89
+ if (missing.length > 0) {
90
+ throw new Error(`Paytm configuration incomplete. Missing fields: ${missing.join(', ')}`);
91
+ }
92
+ }
93
+ function validateRazorpayConfig(config) {
94
+ const required = ['KEY', 'SECRET'];
95
+ const missing = required.filter(field => !config[field]);
96
+ if (missing.length > 0) {
97
+ throw new Error(`Razorpay configuration incomplete. Missing fields: ${missing.join(', ')}`);
98
+ }
99
+ }
100
+ function validatePayUConfig(config) {
101
+ const required = ['KEY', 'SECRET'];
102
+ const missing = required.filter(field => !config[field]);
103
+ if (missing.length > 0) {
104
+ throw new Error(`PayU configuration incomplete. Missing fields: ${missing.join(', ')}`);
105
+ }
106
+ }
107
+ function validateOpenMoneyConfig(config) {
108
+ const required = ['KEY', 'SECRET'];
109
+ const missing = required.filter(field => !config[field]);
110
+ if (missing.length > 0) {
111
+ throw new Error(`OpenMoney configuration incomplete. Missing fields: ${missing.join(', ')}`);
112
+ }
113
+ }
114
+ function buildConfig(userConfig = {}) {
115
+ var _a, _b, _c, _d, _e, _f;
116
+ const envOverrides = pickEnv([
117
+ 'NP_HOST_URL',
118
+ 'NP_PATH_PREFIX',
119
+ 'NP_HOMEPAGE',
120
+ 'NP_TEMPLATE_DIR',
121
+ 'NP_THEME_COLOR',
122
+ 'NP_LOGO',
123
+ 'NP_DB_URL',
124
+ 'NP_PAYTM_URL',
125
+ 'NP_RAZOR_URL',
126
+ 'NP_PAYU_URL',
127
+ 'NP_OPEN_MONEY_URL',
128
+ 'NP_MID',
129
+ 'NP_WEBSITE',
130
+ 'NP_KEY',
131
+ 'NP_SECRET',
132
+ 'NP_CHANNEL_ID',
133
+ 'NP_INDUSTRY_TYPE_ID',
134
+ 'NP_MODE',
135
+ 'NP_THEME_NAME',
136
+ ]);
137
+ const merged = {
138
+ ...defaults,
139
+ ...normalizeEnv(envOverrides),
140
+ ...userConfig,
141
+ };
142
+ // theme normalization
143
+ const themeName = (userConfig.themeName || ((_a = userConfig.theme) === null || _a === void 0 ? void 0 : _a.name) || merged.theme_name || 'dark').toLowerCase();
144
+ const theme = {
145
+ primary: getThemeColor(userConfig, merged),
146
+ accent: ((_b = userConfig === null || userConfig === void 0 ? void 0 : userConfig.theme) === null || _b === void 0 ? void 0 : _b.accent) || merged.theme_accent || '#4ae0ff',
147
+ surface: ((_c = userConfig === null || userConfig === void 0 ? void 0 : userConfig.theme) === null || _c === void 0 ? void 0 : _c.surface) || '#0f1021',
148
+ text: ((_d = userConfig === null || userConfig === void 0 ? void 0 : userConfig.theme) === null || _d === void 0 ? void 0 : _d.text) || '#e9ecf2',
149
+ success: ((_e = userConfig === null || userConfig === void 0 ? void 0 : userConfig.theme) === null || _e === void 0 ? void 0 : _e.success) || '#24cf5f',
150
+ danger: ((_f = userConfig === null || userConfig === void 0 ? void 0 : userConfig.theme) === null || _f === void 0 ? void 0 : _f.danger) || '#ff6b6b',
151
+ name: themeName,
152
+ };
153
+ merged.theme = theme;
154
+ merged.theme_color = theme.primary;
155
+ merged.logo = userConfig.logo || merged.logo;
156
+ merged.brand = userConfig.brand || 'Secure Pay';
157
+ merged.callbacks = userConfig.callbacks || userConfig.hooks || merged.callbacks || {};
158
+ merged.templateDir = userConfig.templateDir || merged.templateDir;
159
+ merged.themeName = themeName;
160
+ // ensure view path remains compatible with legacy controllers
161
+ if (!merged.view_path) {
162
+ merged.view_path = '/../views/';
163
+ }
164
+ if (userConfig.host_url) {
165
+ merged.host_url = userConfig.host_url;
166
+ }
167
+ else if (process.env.NP_HOST_URL) {
168
+ merged.host_url = process.env.NP_HOST_URL;
169
+ }
170
+ return validateConfig(merged);
171
+ }
172
+ function getThemeColor(userConfig, merged) {
173
+ var _a;
174
+ if ((_a = userConfig === null || userConfig === void 0 ? void 0 : userConfig.theme) === null || _a === void 0 ? void 0 : _a.primary)
175
+ return userConfig.theme.primary;
176
+ if (userConfig.theme_color)
177
+ return userConfig.theme_color;
178
+ if (merged.NP_THEME_COLOR)
179
+ return merged.NP_THEME_COLOR;
180
+ return merged.theme_color;
181
+ }
182
+ function normalizeEnv(env) {
183
+ const mapping = {
184
+ NP_HOST_URL: 'host_url',
185
+ NP_PATH_PREFIX: 'path_prefix',
186
+ NP_HOMEPAGE: 'homepage',
187
+ NP_TEMPLATE_DIR: 'templateDir',
188
+ NP_THEME_COLOR: 'theme_color',
189
+ NP_LOGO: 'logo',
190
+ NP_DB_URL: 'db_url',
191
+ NP_PAYTM_URL: 'paytm_url',
192
+ NP_RAZOR_URL: 'razor_url',
193
+ NP_PAYU_URL: 'payu_url',
194
+ NP_OPEN_MONEY_URL: 'open_money_url',
195
+ NP_MID: 'MID',
196
+ NP_WEBSITE: 'WEBSITE',
197
+ NP_KEY: 'KEY',
198
+ NP_SECRET: 'SECRET',
199
+ NP_CHANNEL_ID: 'CHANNEL_ID',
200
+ NP_INDUSTRY_TYPE_ID: 'INDUSTRY_TYPE_ID',
201
+ NP_MODE: 'mode',
202
+ NP_THEME_NAME: 'themeName',
203
+ };
204
+ return Object.keys(env).reduce((acc, key) => {
205
+ const target = mapping[key];
206
+ if (target)
207
+ acc[target] = env[key];
208
+ return acc;
209
+ }, {});
210
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Utils = void 0;
4
+ const IDLEN = 10;
5
+ class Utils {
6
+ static makeid(length = IDLEN) {
7
+ let text = "";
8
+ const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
9
+ for (let i = 0; i < length; i++)
10
+ text += possible.charAt(Math.floor(Math.random() * possible.length));
11
+ return text;
12
+ }
13
+ static sanitizeRequest(body) {
14
+ if (body.amount)
15
+ body.amount = parseFloat(body.amount);
16
+ if (body.TXN_AMOUNT)
17
+ body.amount = parseFloat(body.TXN_AMOUNT);
18
+ }
19
+ }
20
+ exports.Utils = Utils;
@@ -0,0 +1,22 @@
1
+ <p>
2
+ <h4>About</h4>
3
+ <div>
4
+
5
+ <table border="1">
6
+
7
+ <tr>
8
+ <td>Name</td><td>{{name}}</td>
9
+ </tr>
10
+ <tr>
11
+ <td>Version</td><td>{{version}}</td>
12
+ </tr>
13
+ <tr>
14
+ <td>Repository</td><td><a href='{{repository.url}}'>GITHUB</a></td>
15
+ </tr>
16
+
17
+
18
+ </table>
19
+
20
+
21
+ </div>
22
+ </p>
@@ -0,0 +1,98 @@
1
+ <div class="checkout-grid">
2
+ <aside class="product-panel card">
3
+ {{#if productImage}}
4
+ <div class="product-preview">
5
+ <img src="{{productImage}}" alt="{{PRODUCT_NAME}}" style="width:100%;height:100%;object-fit:cover" />
6
+ </div>
7
+ {{/if}}
8
+
9
+ <div class="product-price">
10
+ <div class="pill">Product</div>
11
+ <div class="price">{{PRODUCT_NAME}}</div>
12
+ </div>
13
+ {{#if PRODUCT_DESC}}
14
+ <p class="helper" style="margin-top:14px;">{{PRODUCT_DESC}}</p>
15
+ {{/if}}
16
+ </aside>
17
+
18
+ <section class="form-panel card">
19
+ <header class="card__header">
20
+ <div>
21
+ <p class="eyebrow">Payment</p>
22
+ <h2>Review &amp; pay</h2>
23
+ </div>
24
+ <div class="amount-chip">₹ {{TXN_AMOUNT}}</div>
25
+ </header>
26
+
27
+ <p class="helper">Your payment is processed over an encrypted channel. Double-check the details before you continue.</p>
28
+
29
+ <form action="{{action}}" method="POST" class="form-grid" id="payment-form">
30
+ {{#if check}}
31
+ <label class="field">
32
+ <span class="field__label">Full Name</span>
33
+ <input type="text" name="NAME" required value="{{NAME}}" {{readonly}} />
34
+ </label>
35
+ {{else}}
36
+ <input type="hidden" name="NAME" required value="{{NAME}}" {{readonly}} />
37
+ {{/if}}
38
+
39
+ <label class="field">
40
+ <span class="field__label">Email address</span>
41
+ <input type="email" name="EMAIL" required value="{{EMAIL}}" {{readonly}} />
42
+ </label>
43
+
44
+ <label class="field">
45
+ <span class="field__label">Phone</span>
46
+ <input type="text" name="MOBILE_NO" required value="{{MOBILE_NO}}" {{readonly}} />
47
+ </label>
48
+
49
+ {{#if check}}
50
+ <label class="field">
51
+ <span class="field__label">Product</span>
52
+ <input type="text" name="PRODUCT_NAME" required value="{{PRODUCT_NAME}}" {{readonly}} />
53
+ </label>
54
+ {{else}}
55
+ <input type="hidden" name="PRODUCT_NAME" required value="{{PRODUCT_NAME}}" {{readonly}} />
56
+ {{/if}}
57
+
58
+ <label class="field">
59
+ <span class="field__label">Amount</span>
60
+ <input type="text" name="TXN_AMOUNT" required value="{{TXN_AMOUNT}}" {{readonly}} />
61
+ </label>
62
+
63
+ <div style="grid-column:1 / -1; display:flex; gap:10px; align-items:center; justify-content:space-between;">
64
+ <label class="checkbox" style="margin:0">
65
+ <input type="checkbox" name="save_cc" checked="checked" />
66
+ <span>I agree to the merchant terms</span>
67
+ </label>
68
+ <div style="font-size:12px; color:rgba(255,255,255,0.6)">Secure by PCI-DSS</div>
69
+ </div>
70
+
71
+ <button id="pay-button" type="submit" class="button">{{BUTTON}}</button>
72
+
73
+ <input type="hidden" name="MID" value="{{MID}}" {{readonly}} />
74
+ <input type="hidden" name="WEBSITE" value="{{WEBSITE}}" {{readonly}} />
75
+ <input type="hidden" name="ORDER_ID" value="{{ORDER_ID}}" {{readonly}} />
76
+ <input type="hidden" name="CUST_ID" value="{{CUST_ID}}" {{readonly}} />
77
+ <input type="hidden" name="INDUSTRY_TYPE_ID" value="{{INDUSTRY_TYPE_ID}}" {{readonly}} />
78
+ <input type="hidden" name="CHANNEL_ID" value="{{CHANNEL_ID}}" {{readonly}} />
79
+ <input type="hidden" name="CALLBACK_URL" value="{{CALLBACK_URL}}" {{readonly}} />
80
+ <input type="hidden" name="CHECKSUMHASH" value="{{CHECKSUMHASH}}" {{readonly}} />
81
+ </form>
82
+ </section>
83
+ </div>
84
+
85
+ {{!-- <script>
86
+ (function(){
87
+ if (window.__npPayBtnBound) return;
88
+ window.__npPayBtnBound = true;
89
+ const payBtn = document.getElementById('pay-button');
90
+ if (payBtn) {
91
+ payBtn.addEventListener('click', () => {
92
+ payBtn.setAttribute('data-loading', 'true');
93
+ payBtn.setAttribute('disabled', 'disabled');
94
+ setTimeout(() => payBtn.innerText = 'Processing…', 10);
95
+ });
96
+ }
97
+ })();
98
+ </script> --}}
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{brand}} · Secure Checkout</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.min.css" />
11
+ <link rel="stylesheet" href="/{{path_prefix}}/css/style.css" />
12
+ <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Crect%20width='16'%20height='16'%20rx='3'%20fill='%23ff5722'/%3E%3C/svg%3E" />
13
+ <link rel="shortcut icon" href="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2016%2016'%3E%3Crect%20width='16'%20height='16'%20rx='3'%20fill='%23ff5722'/%3E%3C/svg%3E" />
14
+ <style>
15
+ :root {
16
+ --color-primary: {{#if theme.primary}}{{theme.primary}}{{else}}#2f8bff{{/if}};
17
+ --color-accent: {{#if theme.accent}}{{theme.accent}}{{else}}#5ce1e6{{/if}};
18
+ --color-surface: {{#if theme.surface}}{{theme.surface}}{{else}}#0f1021{{/if}};
19
+ --color-text: {{#if theme.text}}{{theme.text}}{{else}}#e9ecf2{{/if}};
20
+ --color-success: {{#if theme.success}}{{theme.success}}{{else}}#24cf5f{{/if}};
21
+ --color-danger: {{#if theme.danger}}{{theme.danger}}{{else}}#ff6b6b{{/if}};
22
+ --color-outline: rgba(255,255,255,0.08);
23
+ --radius: 14px;
24
+ }
25
+ </style>
26
+ </head>
27
+ <body class="theme-{{#if themeName}}{{themeName}}{{else}}dark{{/if}}">
28
+ <div class="shell">
29
+ <header class="shell__header">
30
+ <div class="brand">
31
+ {{#if logo}}
32
+ <span class="brand__mark brand__mark--img"><img src="{{logo}}" alt="{{brand}} logo"></span>
33
+ {{else}}
34
+ <span class="brand__mark">{{brand}}</span>
35
+ {{/if}}
36
+ <div class="brand__text">
37
+ <div class="brand__title">{{brand}}</div>
38
+ <div class="brand__meta">Secure checkout</div>
39
+ </div>
40
+ </div>
41
+ <div class="pill">Encrypted</div>
42
+ </header>
43
+
44
+ <main class="shell__content">
45
+ {{{body}}}
46
+ </main>
47
+
48
+ <footer class="shell__footer">
49
+ <span>Protected by {{brand}}</span>
50
+ </footer>
51
+ </div>
52
+ </body>
53
+ </html>
@@ -0,0 +1,33 @@
1
+ <section class="card" style="text-align:center; padding:36px 28px;">
2
+ <div style="display:flex; flex-direction:column; align-items:center; gap:18px;" class="status-{{status}}">
3
+ <div style="width:84px; height:84px; border-radius:999px; display:grid; place-items:center; background:rgba(255,255,255,0.03);">
4
+ <svg class="icon-success" width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M20 6L9 17l-5-5" stroke="#4cff9f" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg>
5
+ <svg class="icon-fail" width="40" height="40" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:none"><circle cx="12" cy="12" r="9" stroke="#ff9b9b" stroke-width="2.2"/><path d="M12 8v5" stroke="#ff9b9b" stroke-width="2.2" stroke-linecap="round"/><path d="M12 16h.01" stroke="#ff9b9b" stroke-width="2.2" stroke-linecap="round"/></svg>
6
+ </div>
7
+
8
+ <div>
9
+ <h2 style="margin:0">Payment</h2>
10
+ <p class="helper" style="margin-top:6px">{{message}}</p>
11
+ </div>
12
+
13
+ <div class="result-grid" style="max-width:560px; width:100%;">
14
+ <div class="result-item">
15
+ <p class="label">Order ID</p>
16
+ <p class="value">{{orderId}}</p>
17
+ </div>
18
+ <div class="result-item">
19
+ <p class="label">Transaction ID</p>
20
+ <p class="value">{{txnId}}</p>
21
+ </div>
22
+ <div class="result-item">
23
+ <p class="label">Amount</p>
24
+ <p class="value">{{amount}}</p>
25
+ </div>
26
+ <div class="result-item">
27
+ <p class="label">Status</p>
28
+ <p class="value">{{status}}</p>
29
+ </div>
30
+ </div>
31
+
32
+ </div>
33
+ </section>