elpaypg 0.0.0-stage → 2.0.0

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 (90) hide show
  1. package/.env.example +8 -0
  2. package/README.md +176 -2
  3. package/package.json +21 -4
  4. package/public/elpay-checkout.js +769 -0
  5. package/public/elpay-sms-gateway.apk +0 -0
  6. package/public/elpay-sms-gateway.apk.sha256 +1 -0
  7. package/public/elpay.js +785 -0
  8. package/public/fonts/dm-sans.woff2 +0 -0
  9. package/public/fonts/sora.woff2 +0 -0
  10. package/public/views/checkout.html +2839 -0
  11. package/public/views/partials/auth.html +110 -0
  12. package/public/views/partials/footer.html +54 -0
  13. package/public/views/partials/header.html +101 -0
  14. package/public/views/partials/modals.html +179 -0
  15. package/public/views/partials/payment-methods.html +526 -0
  16. package/public/views/partials/sidebar.html +75 -0
  17. package/public/views/partials/splash.html +35 -0
  18. package/src/__tests__/smoke.test.js +8 -0
  19. package/src/auth-guard.js +95 -0
  20. package/src/index.js +137 -0
  21. package/src/router.js +46 -0
  22. package/src/routes/admin.routes.js +2421 -0
  23. package/src/routes/checkout.routes.js +2000 -0
  24. package/src/routes/customer.routes.js +650 -0
  25. package/src/routes/device.routes.js +303 -0
  26. package/src/routes/payments.routes.js +446 -0
  27. package/src/routes/pollers.routes.js +653 -0
  28. package/src/services/observer-poller.service.js +369 -0
  29. package/src/services/tunnel-manager.js +130 -0
  30. package/src/services/whatsapp-berry.service.js +743 -0
  31. package/src/vendor/common/__tests__/common.test.js +297 -0
  32. package/src/vendor/common/__tests__/common.test.ts +160 -0
  33. package/src/vendor/common/contracts.js +64 -0
  34. package/src/vendor/common/contracts.ts +188 -0
  35. package/src/vendor/common/crypto.js +132 -0
  36. package/src/vendor/common/crypto.ts +112 -0
  37. package/src/vendor/common/enums.js +91 -0
  38. package/src/vendor/common/enums.ts +91 -0
  39. package/src/vendor/common/index.js +12 -0
  40. package/src/vendor/common/index.ts +8 -0
  41. package/src/vendor/common/money.js +62 -0
  42. package/src/vendor/common/money.ts +56 -0
  43. package/src/vendor/common/observation.js +27 -0
  44. package/src/vendor/common/observation.ts +52 -0
  45. package/src/vendor/common/runtime-config.js +246 -0
  46. package/src/vendor/common/secrets.js +374 -0
  47. package/src/vendor/common/state-machine.d.ts +1 -0
  48. package/src/vendor/common/state-machine.js +1 -0
  49. package/src/vendor/common/state-transitions.js +85 -0
  50. package/src/vendor/common/tunnel-command.js +59 -0
  51. package/src/vendor/common/upi-providers.js +389 -0
  52. package/src/vendor/common/upi-providers.ts +400 -0
  53. package/src/vendor/common/upi.js +69 -0
  54. package/src/vendor/common/upi.ts +61 -0
  55. package/src/vendor/core/__tests__/core.test.js +683 -0
  56. package/src/vendor/core/__tests__/queue-timing.test.js +237 -0
  57. package/src/vendor/core/__tests__/webhook-engine.test.js +243 -0
  58. package/src/vendor/core/attribution-engine.js +855 -0
  59. package/src/vendor/core/bank-email-parser.js +105 -0
  60. package/src/vendor/core/bank-sms-parser.js +183 -0
  61. package/src/vendor/core/device-security.js +506 -0
  62. package/src/vendor/core/index.js +13 -0
  63. package/src/vendor/core/normalizer.js +165 -0
  64. package/src/vendor/core/qr-lib.js +8 -0
  65. package/src/vendor/core/qr.js +62 -0
  66. package/src/vendor/core/queue-engine.js +153 -0
  67. package/src/vendor/core/recovery-engine.js +159 -0
  68. package/src/vendor/core/state-machine.js +42 -0
  69. package/src/vendor/core/timing-engine.js +272 -0
  70. package/src/vendor/core/verification-pipeline.js +1164 -0
  71. package/src/vendor/core/webhook-engine.js +280 -0
  72. package/src/vendor/db/__tests__/auth-matrix.test.js +564 -0
  73. package/src/vendor/db/__tests__/customer-reset.test.js +95 -0
  74. package/src/vendor/db/__tests__/db.test.js +265 -0
  75. package/src/vendor/db/__tests__/schema-parity.test.js +137 -0
  76. package/src/vendor/db/__tests__/sqlite_verify.test.js +15 -0
  77. package/src/vendor/db/database.js +181 -0
  78. package/src/vendor/db/index.js +12 -0
  79. package/src/vendor/db/migrate.js +210 -0
  80. package/src/vendor/db/repositories/audit-log.repository.js +68 -0
  81. package/src/vendor/db/repositories/customer-session.repository.js +181 -0
  82. package/src/vendor/db/repositories/customer.repository.js +636 -0
  83. package/src/vendor/db/repositories/device.repository.js +285 -0
  84. package/src/vendor/db/repositories/merchant.repository.js +158 -0
  85. package/src/vendor/db/repositories/observation.repository.js +124 -0
  86. package/src/vendor/db/repositories/payment-session.repository.js +618 -0
  87. package/src/vendor/db/repositories/receiving-account.repository.js +244 -0
  88. package/src/vendor/db/repositories/support-ticket.repository.js +140 -0
  89. package/src/vendor/db/schema.sql +266 -0
  90. package/src/vendor/db/sqlite-schema.sql +270 -0
@@ -0,0 +1,105 @@
1
+ import { toPaise } from '../common/index.js';
2
+ import { extractIntelligentAmount } from './bank-sms-parser.js';
3
+
4
+ export { extractIntelligentAmount };
5
+
6
+ /**
7
+ * Bank email parser for extracting UPI credit alerts across Indian banks:
8
+ * HDFC, ICICI, SBI, Axis, Kotak, Paytm Payments Bank, custom alt-account transfers, etc.
9
+ */
10
+ export class BankEmailParser {
11
+ /**
12
+ * Parses raw email subject and body text into a canonical observation payload.
13
+ * @param {{
14
+ * subject: string,
15
+ * body: string,
16
+ * from?: string,
17
+ * date?: Date | string
18
+ * }} email
19
+ * @returns {{
20
+ * amountPaise: bigint,
21
+ * amount: string,
22
+ * bankReferenceNumber?: string,
23
+ * senderVpa?: string,
24
+ * observedAt: Date,
25
+ * rawSnippet: string
26
+ * } | null}
27
+ */
28
+ static parse(email) {
29
+ if (!email) return null;
30
+ const combinedText = `${email.subject || ''} ${email.body || ''}`.trim();
31
+ if (!combinedText) return null;
32
+
33
+ // Check if email represents an incoming credit/payment. A bare "payment" or "transferred"
34
+ // without credit context is rejected, but "transferred to your account" and "payment of
35
+ // Rs 500" are genuine credits and must still be recognised.
36
+ const creditKeywords = new RegExp(
37
+ '\\b(?:credited|credit|received|deposit(?:ed)?|payment\\s+received|upi\\/cr' +
38
+ '|transferred\\s+to\\s+(?:your\\s+)?(?:a\\/c|account|wallet)' +
39
+ '|payment\\s+of\\s+(?:(?:rs\\b\\.?|inr\\b)\\s*)?[\\d,.]+\\s+(?:has\\s+been\\s+)?made\\s+to\\s+(?:your\\s+)?(?:a\\/c|account|wallet)' +
40
+ '|added\\s+to\\s+(?:your\\s+)?(?:account|wallet))\\b',
41
+ 'i'
42
+ );
43
+ if (!creditKeywords.test(combinedText)) {
44
+ return null; // Ignore non-credit alerts
45
+ }
46
+
47
+ // Reject debit notifications. A debit that appears before any credit means the message is
48
+ // about money leaving the account; a debit mentioned after a credit (e.g. "Rs 500 credited.
49
+ // Rs 10 debited for charges") must not cancel the credit.
50
+ const debitIdx = combinedText.search(/\b(?:debited|withdrawn|deducted|spent|paid rs|sent rs)\b/i);
51
+ const creditIdx = combinedText.search(/\b(?:credited|credit|received|deposit|deposited)\b/i);
52
+ if (debitIdx !== -1 && (creditIdx === -1 || debitIdx < creditIdx)) {
53
+ return null;
54
+ }
55
+
56
+ // 1. Extract Monetary Amount using Intelligent Multi-Pattern Extractor
57
+ const extractedAmount = extractIntelligentAmount(combinedText);
58
+ if (!extractedAmount) {
59
+ return null;
60
+ }
61
+
62
+ // 2. Extract Bank Reference / UTR Number
63
+ // Matches: UTR: 123456789012, Ref no: UTR-HDFC-9911, Ref: UTR-GMAIL-777, UPI/CR/123456789/, 12-digit UTR
64
+ let bankRef;
65
+ const refMatch = combinedText.match(/(?:utr|ref(?:\s*no)?|txn(?:\s*id)?|rrn)[:\s#]+([a-zA-Z0-9\-_]{6,35})/i);
66
+ if (refMatch && refMatch[1]) {
67
+ bankRef = refMatch[1].trim();
68
+ } else {
69
+ const upiCrMatch = combinedText.match(/UPI\/CR\/([a-zA-Z0-9\-_]+)/i);
70
+ if (upiCrMatch && upiCrMatch[1]) {
71
+ bankRef = upiCrMatch[1].trim();
72
+ } else {
73
+ const d12Match = combinedText.match(/\b([0-9]{12})\b/);
74
+ if (d12Match && d12Match[1]) {
75
+ bankRef = d12Match[1].trim();
76
+ }
77
+ }
78
+ }
79
+
80
+ // 3. Extract Sender VPA
81
+ let senderVpa;
82
+ const vpaMatch = combinedText.match(/(?:from|by|vpa)\s+([a-zA-Z0-9.\-_]{2,100}@[a-zA-Z0-9.\-_]{2,64})/i);
83
+ if (vpaMatch && vpaMatch[1]) {
84
+ senderVpa = vpaMatch[1].trim().replace(/[.,;]+$/, '');
85
+ }
86
+
87
+ // 4. Timestamp
88
+ let observedAt = new Date();
89
+ if (email.date) {
90
+ const parsedDate = new Date(email.date);
91
+ if (!isNaN(parsedDate.getTime())) {
92
+ observedAt = parsedDate;
93
+ }
94
+ }
95
+
96
+ return {
97
+ amountPaise: extractedAmount.amountPaise,
98
+ amount: extractedAmount.amountStr,
99
+ bankReferenceNumber: bankRef,
100
+ senderVpa,
101
+ observedAt,
102
+ rawSnippet: combinedText.substring(0, 300)
103
+ };
104
+ }
105
+ }
@@ -0,0 +1,183 @@
1
+ import { toPaise } from '../common/index.js';
2
+
3
+ /**
4
+ * Intelligent Monetary Amount Extractor for Indian UPI & Bank SMS.
5
+ * Extracts amounts with prefix (Rs. 200, INR 500, ₹50), suffix (20.00 Rs),
6
+ * proximity words ("20.00 received", "credited with 20.00"),
7
+ * or standalone decimal amounts ("20.00").
8
+ * Filters out available balance and credit limit clauses so balance amounts are never confused with payment amounts.
9
+ * @param {string} text
10
+ * @returns {{ amountStr: string, amountPaise: bigint } | null}
11
+ */
12
+ export function extractIntelligentAmount(text) {
13
+ if (!text || typeof text !== 'string') return null;
14
+
15
+ // Collapse runs of whitespace before matching. The amount patterns below chain several
16
+ // whitespace-matching quantifiers (`\s*` ... `\s*` ... `[\s:=-]*`), which is polynomial
17
+ // backtracking over a whitespace run: a body of "credited" followed by ~250 spaces took
18
+ // seconds to scan and blocked the payment event loop. With whitespace collapsed to single
19
+ // spaces the scan is linear and the cost is bounded by the length of the text.
20
+ const normalizedText = text.replace(/\s+/g, ' ');
21
+
22
+ // 0. Remove balance mentions (e.g. "Avl Bal Rs 10,000", "Bal: INR 5000", "Available limit Rs. 20,000")
23
+ // to avoid extracting account balance instead of credit amount.
24
+ const balancePattern = /(?:(?:avl\b|avail\b|available\b|tot\b|total\b|ledger\b|clear\b|curr\b|current\b)\s*(?:bal\b|balance\b|limit\b)?|bal\b|balance\b)[\s:=-]*(?:(?:rs\b\.?|inr\b)|₹)?[\s:=-]*[\d,]+(?:\.\d{1,2})?/gi;
25
+ const sanitizedText = normalizedText.replace(balancePattern, ' ').trim();
26
+
27
+ // Pattern 0: Direct credit proximity phrases (e.g. "credited with Rs. 500", "received ₹200.00", "Rs 499.00 credited")
28
+ const directCreditMatch = sanitizedText.match(/(?:credited\s*(?:with|by|for)?|received|deposit(?:ed)?)\s*[:=-]?\s*(?:(?:rs\b\.?|inr\b)|₹)?[\s:=-]*([\d,]+(?:\.\d{1,2})?)/i) ||
29
+ sanitizedText.match(/(?:(?:rs\b\.?|inr\b)|₹)[\s:=-]*([\d,]+(?:\.\d{1,2})?)\s*(?:(?:has\s+been|is)\s+)?(?:credited|received|deposited)/i);
30
+ if (directCreditMatch && (directCreditMatch[1] || directCreditMatch[2])) {
31
+ const clean = (directCreditMatch[1] || directCreditMatch[2]).replace(/,/g, '');
32
+ try {
33
+ return { amountStr: clean, amountPaise: toPaise(clean) };
34
+ } catch {}
35
+ }
36
+
37
+ // Pattern 1: Standard currency prefix on sanitized text (e.g. Rs 499, Rs. 499, ₹499, INR 499, Rs. 20.00, ₹1,200.50)
38
+ const prefixMatch = sanitizedText.match(/(?:(?:rs\b\.?|inr\b)|₹)[\s:=-]*([\d,]+(?:\.\d{1,2})?)/i);
39
+ if (prefixMatch && prefixMatch[1]) {
40
+ const clean = prefixMatch[1].replace(/,/g, '');
41
+ try {
42
+ return { amountStr: clean, amountPaise: toPaise(clean) };
43
+ } catch {}
44
+ }
45
+
46
+ // Pattern 2: Standard currency suffix on sanitized text (e.g. 499 Rs, 499.00 INR, 20.00 Rs, 20rs, 500 inr)
47
+ const suffixMatch = sanitizedText.match(/(?:^|[^\w.])([\d,]+(?:\.\d{1,2})?)[\s:=-]*(?:(?:rs\b\.?|inr\b)|₹)/i);
48
+ if (suffixMatch && suffixMatch[1]) {
49
+ const clean = suffixMatch[1].replace(/,/g, '');
50
+ try {
51
+ return { amountStr: clean, amountPaise: toPaise(clean) };
52
+ } catch {}
53
+ }
54
+
55
+ // Pattern 3: Number adjacent to credit keywords
56
+ const creditProximityMatch = sanitizedText.match(/(?:credited|received|deposit|cr)[\s\w:]*?([\d,]+(?:\.\d{1,2})?)|([\d,]+(?:\.\d{1,2})?)[\s\w:]*?(?:credited|received|deposit|cr)/i);
57
+ if (creditProximityMatch) {
58
+ const rawVal = creditProximityMatch[1] || creditProximityMatch[2];
59
+ if (rawVal) {
60
+ const clean = rawVal.replace(/,/g, '');
61
+ if (/^\d+(\.\d{1,2})?$/.test(clean) && Number(clean) < 10000000 && Number(clean) > 0) {
62
+ try {
63
+ return { amountStr: clean, amountPaise: toPaise(clean) };
64
+ } catch {}
65
+ }
66
+ }
67
+ }
68
+
69
+ // Pattern 4: Standalone decimal currency number (e.g. 20.00, 150.50, 449.00)
70
+ const standaloneDecimal = sanitizedText.match(/\b(\d{1,6}\.\d{2})\b/);
71
+ if (standaloneDecimal && standaloneDecimal[1]) {
72
+ const clean = standaloneDecimal[1];
73
+ try {
74
+ return { amountStr: clean, amountPaise: toPaise(clean) };
75
+ } catch {}
76
+ }
77
+
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Bank and UPI SMS parser for Indian financial institutions:
83
+ * SBI, HDFC, ICICI, Axis, Kotak, PNB, Bank of Baroda, Paytm Payments Bank, PhonePe, GPay, etc.
84
+ */
85
+ export class BankSmsParser {
86
+ /**
87
+ * Parses incoming SMS message text.
88
+ * @param {{
89
+ * text: string,
90
+ * sender?: string,
91
+ * timestamp?: Date | number | string
92
+ * }} sms
93
+ * @returns {{
94
+ * amountPaise: bigint,
95
+ * amount: string,
96
+ * bankReferenceNumber?: string,
97
+ * senderVpa?: string,
98
+ * observedAt: Date,
99
+ * rawText: string
100
+ * } | null}
101
+ */
102
+ static parse(sms) {
103
+ // Bank credit SMS are short. Capping the text bounds the cost of the reference-extraction
104
+ // regexes, which are scanned from every start offset and would otherwise let one
105
+ // oversized message block the event loop (ReDoS / DoS).
106
+ const MAX_SCAN_CHARS = 4096;
107
+ const text = (sms.text || '').trim().slice(0, MAX_SCAN_CHARS);
108
+ if (!text) return null;
109
+
110
+ // Check if SMS represents an incoming credit. A bare "payment" or "transferred" without
111
+ // credit context is rejected, but "transferred to your account" and "payment of Rs 500"
112
+ // are genuine credits and must still be recognised.
113
+ const creditKeywords = new RegExp(
114
+ '\\b(?:credited|received|deposit(?:ed)?|payment\\s+received|money\\s+received|upi\\/cr' +
115
+ '|transferred\\s+to\\s+(?:your\\s+)?(?:a\\/c|account|wallet)' +
116
+ '|payment\\s+of\\s+(?:(?:rs\\b\\.?|inr\\b)\\s*)?[\\d,.]+\\s+(?:has\\s+been\\s+)?made\\s+to\\s+(?:your\\s+)?(?:a\\/c|account|wallet)' +
117
+ '|added\\s+to\\s+(?:your\\s+)?(?:account|wallet))\\b',
118
+ 'i'
119
+ );
120
+ if (!creditKeywords.test(text)) {
121
+ return null;
122
+ }
123
+
124
+ // Reject debit notifications:
125
+ // A debit indicator that appears before any credit indicator means the message is about
126
+ // money leaving the account (e.g. "debited ... credited back in 48h"), so it is refused.
127
+ // A debit mentioned *after* a credit (e.g. "Rs 500 credited. Rs 10 debited for SMS
128
+ // charges") must not cancel the credit, so ordering is what decides.
129
+ const debitIdx = text.search(/\b(?:debited|withdrawn|deducted|spent|paid rs|sent rs)\b/i);
130
+ const creditIdx = text.search(/\b(?:credited|received|deposit|deposited)\b/i);
131
+ if (debitIdx !== -1 && (creditIdx === -1 || debitIdx < creditIdx)) {
132
+ return null;
133
+ }
134
+
135
+ // 1. Extract Amount using Intelligent Multi-Pattern Extractor
136
+ const extractedAmount = extractIntelligentAmount(text);
137
+ if (!extractedAmount) {
138
+ return null;
139
+ }
140
+
141
+ // 2. Extract Bank Reference / UTR / RRN (including FamPay Transaction IDs)
142
+ let bankRef;
143
+ const utrPatterns = [
144
+ /(?:upi\s*ref(?:erence)?\s*(?:no\.?|num)?|utr(?:\s*no\.?)?|rrn(?:\s*no\.?)?|txn\s*(?:id|ref|#)?|transaction\s*id|ref\s*no\.?)[\s:=-]+([a-zA-Z0-9\-_]{6,35})/i,
145
+ /(?:upi\/(?:[0-9]+\/|cr\/)([a-zA-Z0-9\-_]{6,35}))/i,
146
+ /\b(?:ref(?:\s*(?:no\.?|id|#))?)[:\s#=-]+([a-zA-Z0-9\-_]{6,35})/i,
147
+ /\b([0-9]{12})\b/
148
+ ];
149
+
150
+ for (const pat of utrPatterns) {
151
+ const match = text.match(pat);
152
+ if (match && match[1]) {
153
+ bankRef = match[1].trim();
154
+ break;
155
+ }
156
+ }
157
+
158
+ // 3. Extract Sender VPA (including FamPay formats like "from Rahul (UPI ID: rahul@fam)")
159
+ let senderVpa;
160
+ const vpaMatch = text.match(/(?:from|by|vpa|upi\s*id)[\s:]+(?:[a-zA-Z\s]+[\(\[])?([a-zA-Z0-9.\-_]{2,64}@[a-zA-Z0-9.\-_]{2,32})/i);
161
+ if (vpaMatch && vpaMatch[1]) {
162
+ senderVpa = vpaMatch[1].trim().replace(/[.,;]+$/, '');
163
+ }
164
+
165
+ // 4. Resolve Observation Timestamp
166
+ let observedAt = new Date();
167
+ if (sms.timestamp) {
168
+ const parsed = new Date(sms.timestamp);
169
+ if (!isNaN(parsed.getTime())) {
170
+ observedAt = parsed;
171
+ }
172
+ }
173
+
174
+ return {
175
+ amountPaise: extractedAmount.amountPaise,
176
+ amount: extractedAmount.amountStr,
177
+ bankReferenceNumber: bankRef,
178
+ senderVpa,
179
+ observedAt,
180
+ rawText: text
181
+ };
182
+ }
183
+ }