node-paytmpg 5.4.2 → 6.4.3

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.
@@ -10,6 +10,7 @@ var PayU = require('./adapters/payu')
10
10
  const PaytmChecksum = require('./checksum/PaytmChecksum.js');
11
11
  const { stat } = require('fs');
12
12
  const { config } = require('process');
13
+ const path = require('path');
13
14
 
14
15
  let 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">
15
16
  <g transform="rotate(0 50 50)">
@@ -132,7 +133,10 @@ module.exports = function (app, callbacks) {
132
133
  return text;
133
134
  }
134
135
 
135
- var vp = __dirname + config.view_path
136
+ const viewRoot = config.templateDir
137
+ ? config.templateDir
138
+ : path.join(__dirname, '..', 'views');
139
+ var vp = config.resolved_view_path || (viewRoot.endsWith(path.sep) ? viewRoot : viewRoot + path.sep)
136
140
 
137
141
  module.home = (req, res) => {
138
142
 
@@ -144,7 +148,7 @@ module.exports = function (app, callbacks) {
144
148
 
145
149
  module.init = async function (req, res) {
146
150
 
147
- if (!req.body.ORDER_ID && !req.body.EMAIL && req.query.to) {
151
+ if (!req.body.ORDER_ID && !req.body.EMAIL && req.query?.to) {
148
152
  let toData = JSON.parse(nodeBase64.decode(req.query.to));
149
153
  req.body.NAME = toData.NAME
150
154
  req.body.EMAIL = toData.EMAIL
@@ -478,8 +482,9 @@ module.exports = function (app, callbacks) {
478
482
  openMoneyInstance.renderError(params, e, res)
479
483
  }
480
484
  }
481
- if (callbacks !== undefined)
485
+ if (callbacks && typeof callbacks.onStart === 'function') {
482
486
  callbacks.onStart(params['ORDER_ID'], params);
487
+ }
483
488
  }
484
489
  else if ((req.body.ORDER_ID !== undefined && req.body.ORDER_ID.length > 2) || gotAllParams) {
485
490
 
@@ -512,7 +517,7 @@ module.exports = function (app, callbacks) {
512
517
  let showConfirmation =
513
518
  function (err, checksum) {
514
519
  res.render(vp + "init.hbs", {
515
- action: '',
520
+ action:"/"+ config.path_prefix+"/init",
516
521
  readonly: 'readonly',
517
522
  BUTTON: 'Pay',
518
523
  NAME: params['NAME'],
@@ -644,7 +649,7 @@ module.exports = function (app, callbacks) {
644
649
 
645
650
  res.render(vp + "init.hbs", {
646
651
 
647
- action: '',
652
+ action:"/"+ config.path_prefix+"/init",
648
653
  readonly: '',
649
654
  check: true,
650
655
  BUTTON: 'Submit',
@@ -668,77 +673,96 @@ module.exports = function (app, callbacks) {
668
673
 
669
674
  }
670
675
 
671
- function updateTransaction(req, res) {
672
- var myquery = { orderId: req.body.ORDERID };
676
+ async function updateTransaction(req, res) {
677
+ var orderToFind = req.body.ORDERID || req.body.ORDER_ID || req.body.ORDERId || (req.query && req.query.order_id) || req.body.ORDER_ID;
678
+ var myquery = { orderId: orderToFind };
679
+
680
+ let objForUpdate = null;
681
+ try {
682
+ // try default
683
+ objForUpdate = await Transaction.findOne(myquery).catch(() => null);
684
+ // try id
685
+ if (!objForUpdate) objForUpdate = await Transaction.findOne({ id: orderToFind }).catch(() => null);
686
+ // try uppercase key
687
+ if (!objForUpdate) objForUpdate = await Transaction.findOne({ ORDERID: orderToFind }).catch(() => null);
688
+ } catch (e) {
689
+ // ignore lookup errors
690
+ objForUpdate = objForUpdate || null;
691
+ }
692
+
693
+ let returnUrl = objForUpdate ? objForUpdate.returnUrl : null;
694
+ if (returnUrl == 'undefined') returnUrl = undefined;
673
695
 
674
- Transaction.findOne(myquery, function (err, objForUpdate) {
675
- let returnUrl = objForUpdate ? objForUpdate.returnUrl : null;
676
- if (returnUrl == 'undefined') {
677
- returnUrl = undefined
696
+ if (!objForUpdate) {
697
+ if (returnUrl) {
698
+ let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
699
+ returnUrl = returnUrl + separator + 'status=FAILED&message=txn_not_found&ORDERID=' + req.body.ORDERID;
700
+ return res.redirect(returnUrl);
678
701
  }
679
- if (err || !objForUpdate) {
680
- if (returnUrl) {
681
- let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
682
- returnUrl = returnUrl + separator + 'status=FAILED&message=txn_not_found&ORDERID=' + req.body.ORDERID;
683
- return res.redirect(returnUrl);
684
- }
685
- res.send({ message: "Transaction Not Found !", ORDERID: req.body.ORDERID, TXNID: req.body.TXNID })
686
- return;
702
+ return res.send({ message: "Transaction Not Found !", ORDERID: req.body.ORDERID, TXNID: req.body.TXNID });
703
+ }
704
+
705
+ if (objForUpdate.status != ("INITIATED") && objForUpdate.status != ("TXN_PENDING") && objForUpdate.status != ("PENDING")) {
706
+ objForUpdate.readonly = "readonly";
707
+ objForUpdate.action = config.homepage;
708
+ if (returnUrl) {
709
+ let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
710
+ returnUrl = returnUrl + separator + 'status=' + objForUpdate.status + '&ORDERID=' + objForUpdate.orderId + '&TXNID=' + objForUpdate.TXNID;
711
+ return res.redirect(returnUrl);
687
712
  }
688
- if (objForUpdate.status != ("INITIATED") && objForUpdate.status != ("TXN_PENDING") && objForUpdate.status != ("PENDING")) {
689
- objForUpdate.readonly = "readonly"
690
- objForUpdate.action = config.homepage
713
+ else {
714
+ return res.render(vp + "result.hbs", objForUpdate);
715
+ }
716
+ }
717
+
718
+ if (req.body.status == "paid" && !req.body.STATUS) req.body.STATUS = "TXN_SUCCESS";
719
+ objForUpdate.status = req.body.STATUS;
720
+ objForUpdate.TXNID = req.body.TXNID;
721
+ objForUpdate.extra = JSON.stringify(req.body);
722
+
723
+ var newvalues = { $set: objForUpdate };
724
+ Transaction.updateOne(myquery, newvalues, function (err, saveRes) {
725
+ if (err) {
691
726
  if (returnUrl) {
692
727
  let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
693
- returnUrl = returnUrl + separator + 'status=' + objForUpdate.status + '&ORDERID=' + objForUpdate.orderId + '&TXNID=' + objForUpdate.TXNID;
728
+ returnUrl = returnUrl + separator + 'status=FAILED&message=update_error&ORDERID=' + req.body.ORDERID;
694
729
  return res.redirect(returnUrl);
695
730
  }
696
- else {
697
- res.render(vp + "result.hbs", objForUpdate);
698
- }
699
- console.log("Transaction already processed ", req.body.ORDERID)
700
- // res.send({ message: "Transaction already processed", status: objForUpdate.status, ORDERID: objForUpdate.orderId, TXNID: objForUpdate.TXNID, TXNID: req.body.TXNID })
701
- return;
731
+ return res.send({ message: "Error Occured !", ORDERID: req.body.ORDERID, TXNID: req.body.TXNID });
702
732
  }
703
- if (req.body.status == "paid" && !req.body.STATUS) {
704
- req.body.STATUS = "TXN_SUCCESS"
705
- }
706
- objForUpdate.status = req.body.STATUS;
707
- objForUpdate.TXNID = req.body.TXNID;
708
- objForUpdate.extra = JSON.stringify(req.body);
709
-
710
- var newvalues = { $set: objForUpdate };
711
- Transaction.updateOne(myquery, newvalues, function (err, saveRes) {
712
-
713
- if (err) {
714
- if (returnUrl) {
715
- let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
716
- returnUrl = returnUrl + separator + 'status=FAILED&message=update_error&ORDERID=' + req.body.ORDERID;
717
- return res.redirect(returnUrl);
718
- }
719
- res.send({ message: "Error Occured !", ORDERID: req.body.ORDERID, TXNID: req.body.TXNID })
720
- }
721
- else {
722
733
 
723
- if (callbacks !== undefined)
724
- callbacks.onFinish(req.body.ORDERID, req.body);
725
- objForUpdate.readonly = "readonly"
726
- objForUpdate.action = config.homepage
727
- if (returnUrl) {
728
- let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
729
- returnUrl = returnUrl + separator + 'status=' + objForUpdate.status + '&ORDERID=' + objForUpdate.orderId + '&TXNID=' + objForUpdate.TXNID;
730
- return res.redirect(returnUrl);
731
- }
732
- res.render(vp + "result.hbs", objForUpdate);
733
- }
734
- });
735
-
736
- }, usingMultiDbOrm ? Transaction : undefined)
734
+ if (callbacks && typeof callbacks.onFinish === 'function') {
735
+ callbacks.onFinish(req.body.ORDERID, objForUpdate);
736
+ }
737
+ objForUpdate.readonly = "readonly";
738
+ objForUpdate.action = config.homepage;
739
+ if (returnUrl) {
740
+ let separator = returnUrl.indexOf('?') > -1 ? '&' : '?';
741
+ returnUrl = returnUrl + separator + 'status=' + objForUpdate.status + '&ORDERID=' + objForUpdate.orderId + '&TXNID=' + objForUpdate.TXNID;
742
+ return res.redirect(returnUrl);
743
+ }
744
+ res.render(vp + "result.hbs", objForUpdate);
745
+ });
737
746
  }
738
747
 
739
748
  module.callback = async (req, res) => {
740
749
  console.log("request_data ", req.originalUrl, JSON.stringify(req.body))
741
750
 
751
+ // Normalize common order id and txn id field names (support ORDER_ID, ORDERID, etc.)
752
+ try {
753
+ if ((!req.body.ORDERID || req.body.ORDERID === '') && req.body.ORDER_ID) {
754
+ req.body.ORDERID = req.body.ORDER_ID;
755
+ }
756
+ if ((!req.body.TXNID || req.body.TXNID === '') && req.body.TXN_ID) {
757
+ req.body.TXNID = req.body.TXN_ID;
758
+ }
759
+ if ((!req.body.ORDERID || req.body.ORDERID === '') && req.query && req.query.order_id) {
760
+ req.body.ORDERID = req.query.order_id;
761
+ }
762
+ } catch (e) {
763
+ // ignore
764
+ }
765
+
742
766
  var result = false;
743
767
  let isCancelled = false;
744
768
  if (config.paytm_url) {
@@ -1050,8 +1074,9 @@ module.exports = function (app, callbacks) {
1050
1074
  res.send({ message: "Error Occured !", ORDERID: paytmResponse.ORDERID, TXNID: paytmResponse.TXNID })
1051
1075
  }
1052
1076
  else {
1053
- if (callbacks !== undefined)
1077
+ if (callbacks && typeof callbacks.onFinish === 'function') {
1054
1078
  callbacks.onFinish(req.body.ORDER_ID, orderData);
1079
+ }
1055
1080
  res.send(paytmResponse)
1056
1081
  }
1057
1082
  });
@@ -1,93 +1,98 @@
1
- <h2>Payment Start</h2>
2
-
3
- <form action="{{action}}" method="POST">
4
- <ul class="form-list">
5
- {{#if check}}
6
-
7
- <li class="form-list__row">
8
- <label>Name</label>
9
- <input type="text" name="NAME" required="" value="{{NAME}}" {{readonly}} />
10
- </li>
11
- {{else}}
12
- <input type="hidden" name="NAME" required="" value="{{NAME}}" {{readonly}} />
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>
13
26
 
14
- {{/if}}
15
- <li class="form-list__row">
16
- <label>E-Mail</label>
17
- <input type="text" name="EMAIL" required="" value="{{EMAIL}}" {{readonly}} />
18
- </li>
27
+ <p class="helper">Your payment is processed over an encrypted channel. Double-check the details before you continue.</p>
19
28
 
20
- <li class="form-list__row">
21
- <label>Phone</label>
22
- <input type="text" name="MOBILE_NO" required="" value="{{MOBILE_NO}}" {{readonly}} />
23
- </li>
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}}
24
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>
25
43
 
26
- <li class="form-list__row">
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>
27
48
 
28
49
  {{#if check}}
29
-
30
- <div>
31
- <label>
32
- Product
33
- </label>
34
- <input type="text" name="PRODUCT_NAME" required="" value="{{PRODUCT_NAME}}" {{readonly}} />
35
- </div>
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>
36
54
  {{else}}
37
- <input type="hidden" name="PRODUCT_NAME" required="" value="{{PRODUCT_NAME}}" {{readonly}} />
55
+ <input type="hidden" name="PRODUCT_NAME" required value="{{PRODUCT_NAME}}" {{readonly}} />
38
56
  {{/if}}
39
57
 
40
- </li>
41
- <li class="form-list__row">
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>
42
62
 
43
- <div>
44
- <label>
45
- Amount
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>
46
67
  </label>
47
- <input type="text" name="TXN_AMOUNT" required="" value="{{TXN_AMOUNT}}" {{readonly}} />
68
+ <div style="font-size:12px; color:rgba(255,255,255,0.6)">Secure by PCI-DSS</div>
48
69
  </div>
49
70
 
50
- </li>
51
-
52
- {{#if check}}
53
-
54
- <li class="form-list__row form-list__row--agree">
55
- <label>
56
- <input type="checkbox" name="save_cc" checked="checked">
57
- I agree to terms and conditions
58
- </label>
59
- </li>
60
- {{/if}}
61
-
62
- <li>
63
- <button id="show_button" type="submit" class="button">{{BUTTON}}</button>
64
- </li>
65
-
66
- <script>
67
- var button = document.getElementById('show_button')
68
- button.addEventListener('click',hideshow,false);
69
-
70
- function hideshow() {
71
- document.getElementById('show_button').style.display = 'none';
72
- }
73
- </script>
74
-
75
- </ul>
76
-
77
-
78
-
79
- <input type="hidden" name="MID" value="{{MID}}" {{readonly}}>
80
- <input type="hidden" name="WEBSITE" value="{{WEBSITE}}" {{readonly}}>
81
- <input type="hidden" name="ORDER_ID" value="{{ORDER_ID}}" {{readonly}}>
82
- <input type="hidden" name="CUST_ID" value="{{CUST_ID}}" {{readonly}}>
83
- <input type="hidden" name="INDUSTRY_TYPE_ID" value="{{INDUSTRY_TYPE_ID}}" {{readonly}}>
84
- <input type="hidden" name="CHANNEL_ID" value="{{CHANNEL_ID}}" {{readonly}}>
85
- <input type="hidden" name="CALLBACK_URL" value="{{CALLBACK_URL}}" {{readonly}}>
86
- <input type="hidden" name="CHECKSUMHASH" value="{{CHECKSUMHASH}}" {{readonly}}>
87
-
88
-
89
-
90
-
91
-
92
-
93
- </form>
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> --}}
@@ -1,58 +1,53 @@
1
1
  <!DOCTYPE html>
2
- <html>
3
-
2
+ <html lang="en">
4
3
  <head>
5
- <meta charset="UTF-8">
6
- <title>Secure Pay</title>
7
- <link href="https://fonts.googleapis.com/css?family=Open+Sans:400,600" rel="stylesheet">
8
-
9
- <meta name="viewport" content="width=device-width, initial-scale=1">
10
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
11
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
12
-
13
- <link rel="stylesheet" href="css/style.css">
14
-
15
-
16
- </head>
17
-
18
- <body>
19
-
20
- <div class="modal">
21
- <div class="modal__container">
22
-
23
- <div class="modal__content">
24
-
25
-
26
-
27
-
28
- {{{body}}}
29
-
30
-
31
-
32
- </div> <!-- END: .modal__content -->
33
- </div> <!-- END: .modal__container -->
34
- </div> <!-- END: .modal -->
35
- <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js'></script>
36
-
37
- <script src="js/index.js"></script>
38
-
39
-
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="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" />
40
14
  <style>
41
-
42
- input:focus {
43
- border-color: {{theme_color}};
44
- }
45
- /*
46
- body,
47
- .form-list__row label {
48
- color: #cc0f0f;
49
- }
50
- input {
51
- color: #cc0f0f;
15
+ :root {
16
+ --color-primary: {{theme.primary}};
17
+ --color-accent: {{theme.accent}};
18
+ --color-surface: {{theme.surface}};
19
+ --color-text: {{theme.text}};
20
+ --color-success: {{theme.success}};
21
+ --color-danger: {{theme.danger}};
22
+ --color-outline: rgba(255,255,255,0.08);
23
+ --radius: 14px;
52
24
  }
53
-
54
- */
55
25
  </style>
26
+ </head>
27
+ <body class="theme-{{themeName}}">
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>
56
52
  </body>
57
-
58
53
  </html>
@@ -1,50 +1,40 @@
1
- <h2>Payment Status</h2>
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>
2
7
 
3
- <form action="{{action}}" method="POST">
4
- <ul class="form-list">
5
-
8
+ <div>
9
+ <h2 style="margin:0">Payment</h2>
10
+ <p class="helper" style="margin-top:6px">{{message}}</p>
11
+ </div>
6
12
 
7
- <li class="form-list__row">
8
- <label>Order ID</label>
9
- <input type="text" name="NAME" required="" value="{{orderId}}" {{readonly}}/>
10
- </li>
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">Date &amp; Time</p>
28
+ <p class="value">{{date}}</p>
29
+ </div>
30
+ </div>
11
31
 
12
- <li class="form-list__row">
13
- <label>Status</label>
14
- <input type="text" name="NAME" required="" value="{{status}}" {{readonly}}/>
15
- </li>
16
-
17
- <li class="form-list__row">
18
- <label>E-Mail</label>
19
- <input type="text" name="EMAIL" required="" value="{{email}}" {{readonly}}/>
20
- </li>
21
-
22
- <li class="form-list__row">
23
- <label>Phone</label>
24
- <input type="text" name="MOBILE_NO" required="" value="{{phone}}" {{readonly}}/>
25
- </li>
26
-
27
-
28
- <li class="form-list__row form-list__row--inline">
29
-
30
- <div>
31
- <label>
32
- Amount
33
- </label>
34
- <input type="text" name="TXN_AMOUNT" required="" value="{{amount}}" {{readonly}} />
35
- </div>
36
-
37
- </li>
38
-
39
-
40
-
41
- <li>
42
- <button type="submit" class="button">DONE</button>
43
- </li>
44
- </ul>
45
-
46
-
47
-
48
-
49
-
50
- </form>
32
+ <div style="display:flex; gap:12px; margin-top:8px; width:100%; justify-content:center;">
33
+ <div class="success-buttons" style="display:flex; gap:12px;">
34
+ <a href="{{receiptUrl}}" class="button" style="text-decoration:none;">Download Receipt</a>
35
+ <form action="{{homeAction}}" method="GET" style="display:inline"><button type="submit" class="button">Done</button></form>
36
+ </div>
37
+
38
+ </div>
39
+ </div>
40
+ </section>