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,188 @@
1
+ import { PaymentStatus, PublicPaymentOutcome, PaymentMethod, ObserverSource, TicketStatus, SenderType } from './enums.js';
2
+
3
+ // --- Merchant API Contracts ---
4
+ export interface CreatePaymentRequest {
5
+ orderId: string;
6
+ amount: string | number; // e.g. "200.00" or 200
7
+ currency?: string;
8
+ returnUrl?: string;
9
+ method?: PaymentMethod;
10
+ customer?: {
11
+ phoneNumber: string;
12
+ fullName?: string;
13
+ };
14
+ metadata?: Record<string, unknown>;
15
+ }
16
+
17
+ export interface CreatePaymentResponse {
18
+ paymentId: string;
19
+ orderId: string;
20
+ amount: string;
21
+ amountPaise: string;
22
+ currency: string;
23
+ status: PaymentStatus;
24
+ checkoutUrl: string;
25
+ }
26
+
27
+ export interface QueryPaymentResponse {
28
+ paymentId: string;
29
+ orderId: string;
30
+ amount: string;
31
+ currency: string;
32
+ status: PublicPaymentOutcome | PaymentStatus;
33
+ method: PaymentMethod;
34
+ completedAt?: string;
35
+ metadata?: Record<string, unknown>;
36
+ }
37
+
38
+ // --- Checkout Portal Contracts ---
39
+ export interface CheckoutStateResponse {
40
+ paymentId: string;
41
+ orderId: string;
42
+ merchantName: string;
43
+ amount: string;
44
+ amountPaise: string;
45
+ currency: string;
46
+ status: PaymentStatus;
47
+ method: PaymentMethod;
48
+ queuePosition: number;
49
+ timeRemainingSeconds: number;
50
+ upiUri?: string;
51
+ payeeVpa?: string;
52
+ payeeName?: string;
53
+ canClickPaymentDone: boolean;
54
+ branding: {
55
+ appName: string;
56
+ primaryColor: string;
57
+ logoUrl?: string;
58
+ };
59
+ }
60
+
61
+ // --- Webhook Payload Contract ---
62
+ export interface WebhookEventPayload {
63
+ event: PublicPaymentOutcome;
64
+ paymentId: string;
65
+ orderId: string;
66
+ amount: string;
67
+ amountPaise: number;
68
+ currency: string;
69
+ method: PaymentMethod;
70
+ completedAt?: string;
71
+ metadata?: Record<string, unknown>;
72
+ }
73
+
74
+ // --- Observer Payload Contracts ---
75
+ export interface AndroidObservationPayload {
76
+ deviceId: string;
77
+ source: ObserverSource.ANDROID_OBSERVER;
78
+ amountPaise: number;
79
+ observedAt: string;
80
+ observationId?: string;
81
+ senderVpa?: string;
82
+ bankReferenceNumber?: string;
83
+ rawNotification?: Record<string, unknown>;
84
+ }
85
+
86
+ export interface AndroidHeartbeatPayload {
87
+ deviceId: string;
88
+ timestamp: string;
89
+ batteryLevel?: number;
90
+ appVersion?: string;
91
+ }
92
+
93
+ // --- Customer & Support Contracts ---
94
+ export interface CustomerProfile {
95
+ id: string;
96
+ name: string;
97
+ email: string;
98
+ mobileNumber: string;
99
+ status: string;
100
+ lastLoginAt?: string | null;
101
+ createdAt: string;
102
+ updatedAt: string;
103
+ }
104
+
105
+ export interface CustomerRegisterRequest {
106
+ name: string;
107
+ email: string;
108
+ mobileNumber: string;
109
+ password: string;
110
+ }
111
+
112
+ export interface CustomerLoginRequest {
113
+ identifier?: string;
114
+ email?: string;
115
+ mobileNumber?: string;
116
+ password: string;
117
+ }
118
+
119
+ export interface CustomerSessionDto {
120
+ id: string;
121
+ customerId: string;
122
+ token: string;
123
+ expiresAt: string;
124
+ isActive: boolean;
125
+ createdAt: string;
126
+ }
127
+
128
+ /**
129
+ * Normalizes an email address to trimmed lowercase.
130
+ */
131
+ export function normalizeEmail(email: string | null | undefined): string {
132
+ if (!email || typeof email !== 'string') return '';
133
+ return email.trim().toLowerCase();
134
+ }
135
+
136
+ /**
137
+ * Validates email format with standard requirements.
138
+ */
139
+ export function isValidEmail(email: string | null | undefined): boolean {
140
+ const norm = normalizeEmail(email);
141
+ if (!norm) return false;
142
+ return /^[^\s@]+@[^\s@]+\.[a-zA-Z]{2,}$/.test(norm);
143
+ }
144
+
145
+ /**
146
+ * Normalizes a mobile number to canonical 10 digits (stripping prefixes and country codes).
147
+ */
148
+ export function normalizeMobile(mobile: string | null | undefined): string {
149
+ if (!mobile) return '';
150
+ const cleaned = String(mobile).replace(/\D/g, '');
151
+ if (cleaned.length >= 10) {
152
+ return cleaned.slice(-10);
153
+ }
154
+ return cleaned;
155
+ }
156
+
157
+ /**
158
+ * Validates canonical Indian 10-digit mobile number format.
159
+ */
160
+ export function isValidMobile(mobile: string | null | undefined): boolean {
161
+ const norm = normalizeMobile(mobile);
162
+ return /^[6-9]\d{9}$/.test(norm);
163
+ }
164
+
165
+ /**
166
+ * Validates password strength (minimum 6 characters).
167
+ */
168
+ export function isValidPassword(password: string | null | undefined): boolean {
169
+ return typeof password === 'string' && password.length >= 6;
170
+ }
171
+
172
+ export interface SupportTicketSummary {
173
+ id: string;
174
+ subject: string;
175
+ status: TicketStatus;
176
+ paymentId?: string;
177
+ createdAt: string;
178
+ updatedAt: string;
179
+ }
180
+
181
+ export interface TicketMessageDto {
182
+ id: string;
183
+ ticketId: string;
184
+ senderType: SenderType;
185
+ senderId: string;
186
+ message: string;
187
+ createdAt: string;
188
+ }
@@ -0,0 +1,132 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ const ALGORITHM = 'aes-256-gcm';
4
+ const IV_LENGTH = 12; // 96-bit IV recommended for GCM
5
+ const AUTH_TAG_LENGTH = 16; // 128-bit auth tag
6
+
7
+ /**
8
+ * Encrypts plaintext string using AES-256-GCM.
9
+ * Output format: iv_hex:authTag_hex:cipher_hex
10
+ * @param {string} plaintext
11
+ * @param {string} masterKeyHex
12
+ * @returns {string}
13
+ */
14
+ export function encrypt(plaintext, masterKeyHex) {
15
+ const key = Buffer.from(masterKeyHex, 'hex');
16
+ if (key.length !== 32) {
17
+ throw new Error('Master key must be a 32-byte (64 hex characters) string.');
18
+ }
19
+
20
+ const iv = crypto.randomBytes(IV_LENGTH);
21
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
22
+
23
+ const encrypted = Buffer.concat([
24
+ cipher.update(plaintext, 'utf8'),
25
+ cipher.final()
26
+ ]);
27
+
28
+ const authTag = cipher.getAuthTag();
29
+
30
+ return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
31
+ }
32
+
33
+ /**
34
+ * Decrypts an AES-256-GCM ciphertext payload.
35
+ * @param {string} encryptedPayload
36
+ * @param {string} masterKeyHex
37
+ * @returns {string}
38
+ */
39
+ export function decrypt(encryptedPayload, masterKeyHex) {
40
+ const key = Buffer.from(masterKeyHex, 'hex');
41
+ if (key.length !== 32) {
42
+ throw new Error('Master key must be a 32-byte (64 hex characters) string.');
43
+ }
44
+
45
+ const parts = encryptedPayload.split(':');
46
+ if (parts.length !== 3) {
47
+ throw new Error('Invalid encrypted payload format. Expected iv:authTag:cipher.');
48
+ }
49
+
50
+ const iv = Buffer.from(parts[0], 'hex');
51
+ const authTag = Buffer.from(parts[1], 'hex');
52
+ const encryptedText = Buffer.from(parts[2], 'hex');
53
+
54
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
55
+ decipher.setAuthTag(authTag);
56
+
57
+ const decrypted = Buffer.concat([
58
+ decipher.update(encryptedText),
59
+ decipher.final()
60
+ ]);
61
+
62
+ return decrypted.toString('utf8');
63
+ }
64
+
65
+ /**
66
+ * Generates an HMAC-SHA256 signature for webhooks and device authentication.
67
+ * @param {string} data
68
+ * @param {string} secret
69
+ * @returns {string}
70
+ */
71
+ export function generateHmac(data, secret) {
72
+ return crypto.createHmac('sha256', secret).update(data, 'utf8').digest('hex');
73
+ }
74
+
75
+ /**
76
+ * Timing-safe HMAC signature verification.
77
+ * @param {string} data
78
+ * @param {string} secret
79
+ * @param {string} expectedSignature
80
+ * @returns {boolean}
81
+ */
82
+ export function verifyHmac(data, secret, expectedSignature) {
83
+ const actualSignature = generateHmac(data, secret);
84
+ const actualBuffer = Buffer.from(actualSignature, 'hex');
85
+ const expectedBuffer = Buffer.from(expectedSignature, 'hex');
86
+
87
+ if (actualBuffer.length !== expectedBuffer.length) {
88
+ return false;
89
+ }
90
+
91
+ return crypto.timingSafeEqual(actualBuffer, expectedBuffer);
92
+ }
93
+
94
+ /**
95
+ * Generates a cryptographically secure random hex string.
96
+ * @param {number} [bytes=32]
97
+ * @returns {string}
98
+ */
99
+ export function generateRandomToken(bytes = 32) {
100
+ return crypto.randomBytes(bytes).toString('hex');
101
+ }
102
+
103
+ /**
104
+ * Hashes a password using PBKDF2 with SHA-512 and salt.
105
+ * Format: iterations:salt_hex:hash_hex
106
+ * @param {string} password
107
+ * @returns {string}
108
+ */
109
+ export function hashPassword(password) {
110
+ const salt = crypto.randomBytes(16).toString('hex');
111
+ const iterations = 100000;
112
+ const hash = crypto.pbkdf2Sync(password, salt, iterations, 64, 'sha512').toString('hex');
113
+ return `${iterations}:${salt}:${hash}`;
114
+ }
115
+
116
+ /**
117
+ * Verifies a password against a stored PBKDF2 hash.
118
+ * @param {string} password
119
+ * @param {string} storedHash
120
+ * @returns {boolean}
121
+ */
122
+ export function verifyPassword(password, storedHash) {
123
+ const parts = storedHash.split(':');
124
+ if (parts.length !== 3) return false;
125
+
126
+ const iterations = parseInt(parts[0], 10);
127
+ const salt = parts[1];
128
+ const originalHash = parts[2];
129
+
130
+ const checkHash = crypto.pbkdf2Sync(password, salt, iterations, 64, 'sha512').toString('hex');
131
+ return crypto.timingSafeEqual(Buffer.from(checkHash, 'hex'), Buffer.from(originalHash, 'hex'));
132
+ }
@@ -0,0 +1,112 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ const ALGORITHM = 'aes-256-gcm';
4
+ const IV_LENGTH = 12; // 96-bit IV recommended for GCM
5
+ const AUTH_TAG_LENGTH = 16; // 128-bit auth tag
6
+
7
+ /**
8
+ * Encrypts plaintext string using AES-256-GCM.
9
+ * Output format: iv_hex:authTag_hex:cipher_hex
10
+ */
11
+ export function encrypt(plaintext: string, masterKeyHex: string): string {
12
+ const key = Buffer.from(masterKeyHex, 'hex');
13
+ if (key.length !== 32) {
14
+ throw new Error('Master key must be a 32-byte (64 hex characters) string.');
15
+ }
16
+
17
+ const iv = crypto.randomBytes(IV_LENGTH);
18
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
19
+
20
+ const encrypted = Buffer.concat([
21
+ cipher.update(plaintext, 'utf8'),
22
+ cipher.final()
23
+ ]);
24
+
25
+ const authTag = cipher.getAuthTag();
26
+
27
+ return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted.toString('hex')}`;
28
+ }
29
+
30
+ /**
31
+ * Decrypts an AES-256-GCM ciphertext payload.
32
+ */
33
+ export function decrypt(encryptedPayload: string, masterKeyHex: string): string {
34
+ const key = Buffer.from(masterKeyHex, 'hex');
35
+ if (key.length !== 32) {
36
+ throw new Error('Master key must be a 32-byte (64 hex characters) string.');
37
+ }
38
+
39
+ const parts = encryptedPayload.split(':');
40
+ if (parts.length !== 3) {
41
+ throw new Error('Invalid encrypted payload format. Expected iv:authTag:cipher.');
42
+ }
43
+
44
+ const iv = Buffer.from(parts[0]!, 'hex');
45
+ const authTag = Buffer.from(parts[1]!, 'hex');
46
+ const encryptedText = Buffer.from(parts[2]!, 'hex');
47
+
48
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
49
+ decipher.setAuthTag(authTag);
50
+
51
+ const decrypted = Buffer.concat([
52
+ decipher.update(encryptedText),
53
+ decipher.final()
54
+ ]);
55
+
56
+ return decrypted.toString('utf8');
57
+ }
58
+
59
+ /**
60
+ * Generates an HMAC-SHA256 signature for webhooks and device authentication.
61
+ */
62
+ export function generateHmac(data: string, secret: string): string {
63
+ return crypto.createHmac('sha256', secret).update(data, 'utf8').digest('hex');
64
+ }
65
+
66
+ /**
67
+ * Timing-safe HMAC signature verification.
68
+ */
69
+ export function verifyHmac(data: string, secret: string, expectedSignature: string): boolean {
70
+ const actualSignature = generateHmac(data, secret);
71
+ const actualBuffer = Buffer.from(actualSignature, 'hex');
72
+ const expectedBuffer = Buffer.from(expectedSignature, 'hex');
73
+
74
+ if (actualBuffer.length !== expectedBuffer.length) {
75
+ return false;
76
+ }
77
+
78
+ return crypto.timingSafeEqual(actualBuffer, expectedBuffer);
79
+ }
80
+
81
+ /**
82
+ * Generates a cryptographically secure random hex string.
83
+ */
84
+ export function generateRandomToken(bytes: number = 32): string {
85
+ return crypto.randomBytes(bytes).toString('hex');
86
+ }
87
+
88
+ /**
89
+ * Hashes a password using PBKDF2 with SHA-512 and salt.
90
+ * Format: iterations:salt_hex:hash_hex
91
+ */
92
+ export function hashPassword(password: string): string {
93
+ const salt = crypto.randomBytes(16).toString('hex');
94
+ const iterations = 100000;
95
+ const hash = crypto.pbkdf2Sync(password, salt, iterations, 64, 'sha512').toString('hex');
96
+ return `${iterations}:${salt}:${hash}`;
97
+ }
98
+
99
+ /**
100
+ * Verifies a password against a stored PBKDF2 hash.
101
+ */
102
+ export function verifyPassword(password: string, storedHash: string): boolean {
103
+ const parts = storedHash.split(':');
104
+ if (parts.length !== 3) return false;
105
+
106
+ const iterations = parseInt(parts[0]!, 10);
107
+ const salt = parts[1]!;
108
+ const originalHash = parts[2]!;
109
+
110
+ const checkHash = crypto.pbkdf2Sync(password, salt, iterations, 64, 'sha512').toString('hex');
111
+ return crypto.timingSafeEqual(Buffer.from(checkHash, 'hex'), Buffer.from(originalHash, 'hex'));
112
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Internal high-resolution payment session lifecycle states.
3
+ */
4
+ export const PaymentStatus = Object.freeze({
5
+ INITIATED: 'INITIATED',
6
+ QUEUED: 'QUEUED',
7
+ ACTIVE: 'ACTIVE',
8
+ PROCESSING: 'PROCESSING',
9
+ SUCCESS: 'SUCCESS',
10
+ FAILED: 'FAILED',
11
+ EXPIRED: 'EXPIRED',
12
+ CANCELLED: 'CANCELLED'
13
+ });
14
+
15
+ /**
16
+ * Public-facing payment outcomes consumed by external merchants.
17
+ */
18
+ export const PublicPaymentOutcome = Object.freeze({
19
+ SUCCESS: 'SUCCESS',
20
+ FAILED: 'FAILED',
21
+ EXPIRED: 'EXPIRED',
22
+ CANCELLED: 'CANCELLED'
23
+ });
24
+
25
+ /**
26
+ * Supported payment modalities.
27
+ */
28
+ export const PaymentMethod = Object.freeze({
29
+ UPI: 'UPI',
30
+ COD: 'COD',
31
+ CASH_COUNTER: 'CASH_COUNTER',
32
+ CARD_COUNTER: 'CARD_COUNTER'
33
+ });
34
+
35
+ /**
36
+ * Merchant gateway selling modes.
37
+ */
38
+ export const SellingMode = Object.freeze({
39
+ ONLINE: 'ONLINE',
40
+ OFFLINE: 'OFFLINE'
41
+ });
42
+
43
+ /**
44
+ * Observation channel sources feeding the payment detection engine (2-Layer: SMS + Gmail).
45
+ * Android observer layer is completely removed.
46
+ */
47
+ export const ObserverSource = Object.freeze({
48
+ GMAIL: 'GMAIL',
49
+ GOOGLE_MESSAGES: 'GOOGLE_MESSAGES',
50
+ ANDROID_OBSERVER: 'ANDROID_OBSERVER',
51
+ SMS_GATEWAY: 'SMS_GATEWAY',
52
+ MANUAL_COUNTER: 'MANUAL_COUNTER'
53
+ });
54
+
55
+ /**
56
+ * Attribution status for raw observations.
57
+ */
58
+ export const ObservationAttributionStatus = Object.freeze({
59
+ UNATTRIBUTED: 'UNATTRIBUTED',
60
+ ATTRIBUTED: 'ATTRIBUTED',
61
+ LATE_UNMATCHED: 'LATE_UNMATCHED',
62
+ DUPLICATE_IGNORED: 'DUPLICATE_IGNORED'
63
+ });
64
+
65
+ /**
66
+ * Support ticket lifecycle states.
67
+ */
68
+ export const TicketStatus = Object.freeze({
69
+ OPEN: 'OPEN',
70
+ IN_PROGRESS: 'IN_PROGRESS',
71
+ RESOLVED: 'RESOLVED',
72
+ CLOSED: 'CLOSED'
73
+ });
74
+
75
+ /**
76
+ * Live chat message sender identity type.
77
+ */
78
+ export const SenderType = Object.freeze({
79
+ CUSTOMER: 'CUSTOMER',
80
+ ADMIN: 'ADMIN'
81
+ });
82
+
83
+ /**
84
+ * Audit log actor classifications.
85
+ */
86
+ export const AuditActorType = Object.freeze({
87
+ SYSTEM: 'SYSTEM',
88
+ CUSTOMER: 'CUSTOMER',
89
+ ADMIN: 'ADMIN',
90
+ OBSERVER: 'OBSERVER'
91
+ });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Internal high-resolution payment session lifecycle states.
3
+ */
4
+ export enum PaymentStatus {
5
+ INITIATED = 'INITIATED',
6
+ QUEUED = 'QUEUED',
7
+ ACTIVE = 'ACTIVE',
8
+ PROCESSING = 'PROCESSING',
9
+ SUCCESS = 'SUCCESS',
10
+ FAILED = 'FAILED',
11
+ EXPIRED = 'EXPIRED',
12
+ CANCELLED = 'CANCELLED'
13
+ }
14
+
15
+ /**
16
+ * Public-facing payment outcomes consumed by external merchants.
17
+ */
18
+ export enum PublicPaymentOutcome {
19
+ SUCCESS = 'SUCCESS',
20
+ FAILED = 'FAILED',
21
+ EXPIRED = 'EXPIRED',
22
+ CANCELLED = 'CANCELLED'
23
+ }
24
+
25
+ /**
26
+ * Supported payment modalities.
27
+ */
28
+ export enum PaymentMethod {
29
+ UPI = 'UPI',
30
+ COD = 'COD',
31
+ CASH_COUNTER = 'CASH_COUNTER',
32
+ CARD_COUNTER = 'CARD_COUNTER'
33
+ }
34
+
35
+ /**
36
+ * Merchant gateway selling modes.
37
+ */
38
+ export enum SellingMode {
39
+ ONLINE = 'ONLINE',
40
+ OFFLINE = 'OFFLINE'
41
+ }
42
+
43
+ /**
44
+ * Observation channel sources feeding the payment detection engine (2-Layer: SMS + Gmail).
45
+ * Android observer layer is completely removed.
46
+ */
47
+ export enum ObserverSource {
48
+ GMAIL = 'GMAIL',
49
+ GOOGLE_MESSAGES = 'GOOGLE_MESSAGES',
50
+ ANDROID_OBSERVER = 'ANDROID_OBSERVER',
51
+ SMS_GATEWAY = 'SMS_GATEWAY',
52
+ MANUAL_COUNTER = 'MANUAL_COUNTER'
53
+ }
54
+
55
+ /**
56
+ * Attribution status for raw observations.
57
+ */
58
+ export enum ObservationAttributionStatus {
59
+ UNATTRIBUTED = 'UNATTRIBUTED',
60
+ ATTRIBUTED = 'ATTRIBUTED',
61
+ LATE_UNMATCHED = 'LATE_UNMATCHED',
62
+ DUPLICATE_IGNORED = 'DUPLICATE_IGNORED'
63
+ }
64
+
65
+ /**
66
+ * Support ticket lifecycle states.
67
+ */
68
+ export enum TicketStatus {
69
+ OPEN = 'OPEN',
70
+ IN_PROGRESS = 'IN_PROGRESS',
71
+ RESOLVED = 'RESOLVED',
72
+ CLOSED = 'CLOSED'
73
+ }
74
+
75
+ /**
76
+ * Live chat message sender identity type.
77
+ */
78
+ export enum SenderType {
79
+ CUSTOMER = 'CUSTOMER',
80
+ ADMIN = 'ADMIN'
81
+ }
82
+
83
+ /**
84
+ * Audit log actor classifications.
85
+ */
86
+ export enum AuditActorType {
87
+ SYSTEM = 'SYSTEM',
88
+ CUSTOMER = 'CUSTOMER',
89
+ ADMIN = 'ADMIN',
90
+ OBSERVER = 'OBSERVER'
91
+ }
@@ -0,0 +1,12 @@
1
+ export * from './enums.js';
2
+ export * from './state-transitions.js';
3
+ export * from './money.js';
4
+ export * from './crypto.js';
5
+ export * from './observation.js';
6
+ export * from './upi.js';
7
+ export * from './upi-providers.js';
8
+ export * from './contracts.js';
9
+ export * from './runtime-config.js';
10
+ export * from './secrets.js';
11
+ export * from './tunnel-command.js';
12
+ export * from './state-machine.js';
@@ -0,0 +1,8 @@
1
+ export * from './enums.js';
2
+ export * from './money.js';
3
+ export * from './crypto.js';
4
+ export * from './observation.js';
5
+ export * from './upi.js';
6
+ export * from './upi-providers.js';
7
+ export * from './contracts.js';
8
+ export * from './state-machine.js';
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Financial utilities enforcing integer minor units (paise).
3
+ * Floating-point representation is strictly forbidden in financial calculations.
4
+ * 1 INR = 100 paise.
5
+ */
6
+
7
+ /**
8
+ * Converts a rupee amount (string or number) into integer paise (bigint).
9
+ * Throws an error if invalid, negative, zero, or exceeds 2 decimal places.
10
+ * @param {string | number} rupees
11
+ * @returns {bigint}
12
+ */
13
+ export function toPaise(rupees) {
14
+ if (typeof rupees === 'number') {
15
+ if (!Number.isFinite(rupees) || Number.isNaN(rupees)) {
16
+ throw new Error(`Invalid monetary amount: ${rupees}`);
17
+ }
18
+ rupees = rupees.toString();
19
+ }
20
+
21
+ const clean = String(rupees).trim();
22
+ if (!/^\d+(\.\d{1,2})?$/.test(clean)) {
23
+ throw new Error(`Invalid INR rupee format: "${rupees}". Must be non-negative with max 2 decimals.`);
24
+ }
25
+
26
+ const parts = clean.split('.');
27
+ const whole = BigInt(parts[0] || '0');
28
+ const fractionStr = parts[1] || '';
29
+ const fraction = BigInt(fractionStr.padEnd(2, '0'));
30
+
31
+ const totalPaise = whole * 100n + fraction;
32
+ if (totalPaise <= 0n) {
33
+ throw new Error(`Monetary amount must be strictly greater than 0: ${rupees}`);
34
+ }
35
+
36
+ return totalPaise;
37
+ }
38
+
39
+ /**
40
+ * Formats integer paise into a standard decimal string ("200.00").
41
+ * @param {bigint | number} paise
42
+ * @returns {string}
43
+ */
44
+ export function toRupeesString(paise) {
45
+ const p = typeof paise === 'number' ? BigInt(Math.floor(paise)) : BigInt(paise);
46
+ if (p < 0n) {
47
+ throw new Error(`Negative paise value not permitted: ${p}`);
48
+ }
49
+
50
+ const whole = p / 100n;
51
+ const fraction = p % 100n;
52
+ return `${whole}.${fraction.toString().padStart(2, '0')}`;
53
+ }
54
+
55
+ /**
56
+ * Formats integer paise into a localized INR display string ("₹200.00").
57
+ * @param {bigint | number} paise
58
+ * @returns {string}
59
+ */
60
+ export function formatINR(paise) {
61
+ return `₹${toRupeesString(paise)}`;
62
+ }