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.
- package/.env.example +8 -0
- package/README.md +176 -2
- package/package.json +21 -4
- package/public/elpay-checkout.js +769 -0
- package/public/elpay-sms-gateway.apk +0 -0
- package/public/elpay-sms-gateway.apk.sha256 +1 -0
- package/public/elpay.js +785 -0
- package/public/fonts/dm-sans.woff2 +0 -0
- package/public/fonts/sora.woff2 +0 -0
- package/public/views/checkout.html +2839 -0
- package/public/views/partials/auth.html +110 -0
- package/public/views/partials/footer.html +54 -0
- package/public/views/partials/header.html +101 -0
- package/public/views/partials/modals.html +179 -0
- package/public/views/partials/payment-methods.html +526 -0
- package/public/views/partials/sidebar.html +75 -0
- package/public/views/partials/splash.html +35 -0
- package/src/__tests__/smoke.test.js +8 -0
- package/src/auth-guard.js +95 -0
- package/src/index.js +137 -0
- package/src/router.js +46 -0
- package/src/routes/admin.routes.js +2421 -0
- package/src/routes/checkout.routes.js +2000 -0
- package/src/routes/customer.routes.js +650 -0
- package/src/routes/device.routes.js +303 -0
- package/src/routes/payments.routes.js +446 -0
- package/src/routes/pollers.routes.js +653 -0
- package/src/services/observer-poller.service.js +369 -0
- package/src/services/tunnel-manager.js +130 -0
- package/src/services/whatsapp-berry.service.js +743 -0
- package/src/vendor/common/__tests__/common.test.js +297 -0
- package/src/vendor/common/__tests__/common.test.ts +160 -0
- package/src/vendor/common/contracts.js +64 -0
- package/src/vendor/common/contracts.ts +188 -0
- package/src/vendor/common/crypto.js +132 -0
- package/src/vendor/common/crypto.ts +112 -0
- package/src/vendor/common/enums.js +91 -0
- package/src/vendor/common/enums.ts +91 -0
- package/src/vendor/common/index.js +12 -0
- package/src/vendor/common/index.ts +8 -0
- package/src/vendor/common/money.js +62 -0
- package/src/vendor/common/money.ts +56 -0
- package/src/vendor/common/observation.js +27 -0
- package/src/vendor/common/observation.ts +52 -0
- package/src/vendor/common/runtime-config.js +246 -0
- package/src/vendor/common/secrets.js +374 -0
- package/src/vendor/common/state-machine.d.ts +1 -0
- package/src/vendor/common/state-machine.js +1 -0
- package/src/vendor/common/state-transitions.js +85 -0
- package/src/vendor/common/tunnel-command.js +59 -0
- package/src/vendor/common/upi-providers.js +389 -0
- package/src/vendor/common/upi-providers.ts +400 -0
- package/src/vendor/common/upi.js +69 -0
- package/src/vendor/common/upi.ts +61 -0
- package/src/vendor/core/__tests__/core.test.js +683 -0
- package/src/vendor/core/__tests__/queue-timing.test.js +237 -0
- package/src/vendor/core/__tests__/webhook-engine.test.js +243 -0
- package/src/vendor/core/attribution-engine.js +855 -0
- package/src/vendor/core/bank-email-parser.js +105 -0
- package/src/vendor/core/bank-sms-parser.js +183 -0
- package/src/vendor/core/device-security.js +506 -0
- package/src/vendor/core/index.js +13 -0
- package/src/vendor/core/normalizer.js +165 -0
- package/src/vendor/core/qr-lib.js +8 -0
- package/src/vendor/core/qr.js +62 -0
- package/src/vendor/core/queue-engine.js +153 -0
- package/src/vendor/core/recovery-engine.js +159 -0
- package/src/vendor/core/state-machine.js +42 -0
- package/src/vendor/core/timing-engine.js +272 -0
- package/src/vendor/core/verification-pipeline.js +1164 -0
- package/src/vendor/core/webhook-engine.js +280 -0
- package/src/vendor/db/__tests__/auth-matrix.test.js +564 -0
- package/src/vendor/db/__tests__/customer-reset.test.js +95 -0
- package/src/vendor/db/__tests__/db.test.js +265 -0
- package/src/vendor/db/__tests__/schema-parity.test.js +137 -0
- package/src/vendor/db/__tests__/sqlite_verify.test.js +15 -0
- package/src/vendor/db/database.js +181 -0
- package/src/vendor/db/index.js +12 -0
- package/src/vendor/db/migrate.js +210 -0
- package/src/vendor/db/repositories/audit-log.repository.js +68 -0
- package/src/vendor/db/repositories/customer-session.repository.js +181 -0
- package/src/vendor/db/repositories/customer.repository.js +636 -0
- package/src/vendor/db/repositories/device.repository.js +285 -0
- package/src/vendor/db/repositories/merchant.repository.js +158 -0
- package/src/vendor/db/repositories/observation.repository.js +124 -0
- package/src/vendor/db/repositories/payment-session.repository.js +618 -0
- package/src/vendor/db/repositories/receiving-account.repository.js +244 -0
- package/src/vendor/db/repositories/support-ticket.repository.js +140 -0
- package/src/vendor/db/schema.sql +266 -0
- 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
|
+
}
|