ts-client-lib 0.0.7

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 (64) hide show
  1. package/README.md +76 -0
  2. package/auth/TSJWT.d.ts +29 -0
  3. package/auth/TSJWT.js +44 -0
  4. package/auth/TSOAuth.d.ts +132 -0
  5. package/auth/TSOAuth.js +230 -0
  6. package/devices/TSCordova.d.ts +5 -0
  7. package/devices/TSCordova.js +52 -0
  8. package/entities/TSCountries.d.ts +14 -0
  9. package/entities/TSCountries.js +1188 -0
  10. package/entities/TSCurrencies.d.ts +35 -0
  11. package/entities/TSCurrencies.js +604 -0
  12. package/entities/TSMobilePhones.d.ts +167 -0
  13. package/entities/TSMobilePhones.js +206 -0
  14. package/entities/TSMoney.d.ts +149 -0
  15. package/entities/TSMoney.js +311 -0
  16. package/entities/currency-amount.d.ts +13 -0
  17. package/entities/currency-amount.js +43 -0
  18. package/finance/TSBonus.d.ts +197 -0
  19. package/finance/TSBonus.js +530 -0
  20. package/finance/TSKYC.d.ts +563 -0
  21. package/finance/TSKYC.js +1066 -0
  22. package/finance/TSTax.d.ts +49 -0
  23. package/finance/TSTax.js +106 -0
  24. package/finance/bonus-money.d.ts +41 -0
  25. package/finance/bonus-money.js +61 -0
  26. package/games/TSBetSlip.d.ts +72 -0
  27. package/games/TSBetSlip.js +179 -0
  28. package/games/TSBetSystem.d.ts +4 -0
  29. package/games/TSBetSystem.js +48 -0
  30. package/games/TSLotto.d.ts +35 -0
  31. package/games/TSLotto.js +205 -0
  32. package/games/TSPool.d.ts +28 -0
  33. package/games/TSPool.js +88 -0
  34. package/package.json +93 -0
  35. package/utils/TSArray.d.ts +9 -0
  36. package/utils/TSArray.js +87 -0
  37. package/utils/TSBoolean.d.ts +4 -0
  38. package/utils/TSBoolean.js +24 -0
  39. package/utils/TSCache.d.ts +167 -0
  40. package/utils/TSCache.js +531 -0
  41. package/utils/TSDate.d.ts +8 -0
  42. package/utils/TSDate.js +67 -0
  43. package/utils/TSHeuristic.d.ts +20 -0
  44. package/utils/TSHeuristic.js +197 -0
  45. package/utils/TSLZS.d.ts +42 -0
  46. package/utils/TSLZS.js +343 -0
  47. package/utils/TSLog.d.ts +40 -0
  48. package/utils/TSLog.js +110 -0
  49. package/utils/TSNumber.d.ts +6 -0
  50. package/utils/TSNumber.js +68 -0
  51. package/utils/TSObject.d.ts +29 -0
  52. package/utils/TSObject.js +312 -0
  53. package/utils/TSPagination.d.ts +282 -0
  54. package/utils/TSPagination.js +425 -0
  55. package/utils/TSPaginationMulti.d.ts +77 -0
  56. package/utils/TSPaginationMulti.js +356 -0
  57. package/utils/TSString.d.ts +10 -0
  58. package/utils/TSString.js +107 -0
  59. package/utils/TSValidator.d.ts +16 -0
  60. package/utils/TSValidator.js +74 -0
  61. package/utils/TSWorker.d.ts +3 -0
  62. package/utils/TSWorker.js +32 -0
  63. package/utils/diacritics-removal-map.d.ts +5 -0
  64. package/utils/diacritics-removal-map.js +341 -0
@@ -0,0 +1,1066 @@
1
+ "use strict";
2
+ /**
3
+ * KYCEligibility - Client-Safe KYC Validation & Eligibility Checker
4
+ *
5
+ * A static class for checking KYC eligibility, limits, and requirements on client-side.
6
+ * No database dependencies - pure functions only.
7
+ *
8
+ * Usage:
9
+ * ```typescript
10
+ * import { KYCEligibility } from 'ts-client-lib/finance/TSKyc';
11
+ *
12
+ * // Check if user can make a transaction
13
+ * const result = KYCEligibility.checkTransaction(limits, {
14
+ * currentTier: 'basic',
15
+ * transactionType: 'withdrawal',
16
+ * amount: 5000,
17
+ * currency: 'EUR',
18
+ * usedToday: 1000,
19
+ * usedThisMonth: 10000,
20
+ * });
21
+ *
22
+ * if (!result.allowed) {
23
+ * console.log(result.reason);
24
+ * console.log(`Remaining: ${result.remaining}`);
25
+ * }
26
+ *
27
+ * // Get requirements for next tier
28
+ * const requirements = KYCEligibility.getTierRequirements('enhanced');
29
+ *
30
+ * // Check if user can access a feature
31
+ * const canWithdraw = KYCEligibility.canPerformAction('withdrawal', 'basic', 'standard');
32
+ * ```
33
+ */
34
+ var __assign = (this && this.__assign) || function () {
35
+ __assign = Object.assign || function(t) {
36
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
37
+ s = arguments[i];
38
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
39
+ t[p] = s[p];
40
+ }
41
+ return t;
42
+ };
43
+ return __assign.apply(this, arguments);
44
+ };
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.prevalidateKYCTransaction = exports.getRemainingLimits = exports.tierMeetsRequirement = exports.canPerformAction = exports.checkTierEligibility = exports.getTierRequirements = exports.checkKYCTransaction = exports.KYCEligibility = exports.KYC_VALIDATOR_ERROR_CODES = exports.KYC_VALIDATOR_ERRORS = void 0;
47
+ exports.canonicalLimitKey = canonicalLimitKey;
48
+ exports.KYC_VALIDATOR_ERRORS = {
49
+ UnknownKYCTier: 'MSKYCUnknownKYCTier',
50
+ FileTooLarge: 'MSKYCFileTooLarge',
51
+ FileTypeNotAllowed: 'MSKYCFileTypeNotAllowed',
52
+ FileExtensionNotAllowed: 'MSKYCFileExtensionNotAllowed',
53
+ // ─── Pre-validation rejection codes (client + server share these so i18n stays consistent) ───
54
+ /** KYC status is not approved or in-review (e.g. rejected/suspended/expired). */
55
+ KycStatusNotAllowed: 'MSKYCStatusNotAllowed',
56
+ /** Stored `expiresAt` is past. Distinct from `KycStatusNotAllowed` so UIs can prompt re-verification. */
57
+ KycExpired: 'MSKYCExpired',
58
+ /** Account flagged suspended at the operator/auth layer. */
59
+ AccountSuspended: 'MSKYCAccountSuspended',
60
+ /** Self-exclusion window is active. */
61
+ SelfExcluded: 'MSKYCSelfExcluded',
62
+ /** Wallet status is not 'active' (suspended/closed). */
63
+ WalletNotActive: 'MSKYCWalletNotActive',
64
+ /** Wallet balance < amount on an outflow operation. */
65
+ InsufficientBalance: 'MSKYCInsufficientBalance',
66
+ /** Operation type has no limit profile at this tier (e.g. transfer at tier 'none'). */
67
+ TierNotPermitted: 'MSKYCTierNotPermitted',
68
+ /** Amount below the per-transaction min. */
69
+ AmountBelowMin: 'MSKYCAmountBelowMin',
70
+ /** Amount above the per-transaction max (upgrade may help). */
71
+ AmountAboveMax: 'MSKYCAmountAboveMax',
72
+ /** Daily aggregate cap would be exceeded. */
73
+ DailyLimitExceeded: 'MSKYCDailyLimitExceeded',
74
+ /** Weekly aggregate cap would be exceeded. */
75
+ WeeklyLimitExceeded: 'MSKYCWeeklyLimitExceeded',
76
+ /** Monthly aggregate cap would be exceeded. */
77
+ MonthlyLimitExceeded: 'MSKYCMonthlyLimitExceeded',
78
+ /** Per-period transaction-count cap reached. */
79
+ TransactionCountExceeded: 'MSKYCTransactionCountExceeded',
80
+ /** Deposit would push wallet balance above the tier's max-balance ceiling. */
81
+ MaxBalanceExceeded: 'MSKYCMaxBalanceExceeded'
82
+ };
83
+ exports.KYC_VALIDATOR_ERROR_CODES = [
84
+ exports.KYC_VALIDATOR_ERRORS.UnknownKYCTier,
85
+ exports.KYC_VALIDATOR_ERRORS.FileTooLarge,
86
+ exports.KYC_VALIDATOR_ERRORS.FileTypeNotAllowed,
87
+ exports.KYC_VALIDATOR_ERRORS.FileExtensionNotAllowed,
88
+ exports.KYC_VALIDATOR_ERRORS.KycStatusNotAllowed,
89
+ exports.KYC_VALIDATOR_ERRORS.KycExpired,
90
+ exports.KYC_VALIDATOR_ERRORS.AccountSuspended,
91
+ exports.KYC_VALIDATOR_ERRORS.SelfExcluded,
92
+ exports.KYC_VALIDATOR_ERRORS.WalletNotActive,
93
+ exports.KYC_VALIDATOR_ERRORS.InsufficientBalance,
94
+ exports.KYC_VALIDATOR_ERRORS.TierNotPermitted,
95
+ exports.KYC_VALIDATOR_ERRORS.AmountBelowMin,
96
+ exports.KYC_VALIDATOR_ERRORS.AmountAboveMax,
97
+ exports.KYC_VALIDATOR_ERRORS.DailyLimitExceeded,
98
+ exports.KYC_VALIDATOR_ERRORS.WeeklyLimitExceeded,
99
+ exports.KYC_VALIDATOR_ERRORS.MonthlyLimitExceeded,
100
+ exports.KYC_VALIDATOR_ERRORS.TransactionCountExceeded,
101
+ exports.KYC_VALIDATOR_ERRORS.MaxBalanceExceeded
102
+ ];
103
+ /**
104
+ * Map operation type → tier-limit profile key. `KYCTierLimits` stores three profiles
105
+ * (deposit / withdrawal / transfer); `order` reuses `deposit`. Anything else is invalid
106
+ * at the type level so this is exhaustive.
107
+ *
108
+ * **PXE-7** — `tierLimits[X]` MUST go through this helper. Raw `tierLimits[type]` returns
109
+ * undefined for `'order'` and silently bypasses tier-limit enforcement.
110
+ * See graphify/pre-execution/cross-product-admission-canonical.md.
111
+ */
112
+ function canonicalLimitKey(t) {
113
+ return t === 'order' ? 'deposit' : t;
114
+ }
115
+ // ═══════════════════════════════════════════════════════════════════════════
116
+ // CONSTANTS
117
+ // ═══════════════════════════════════════════════════════════════════════════
118
+ /**
119
+ * Tier hierarchy (lower index = lower tier)
120
+ */
121
+ var TIER_LEVELS = {
122
+ 'none': 0,
123
+ 'basic': 1,
124
+ 'standard': 2,
125
+ 'enhanced': 3,
126
+ 'full': 4,
127
+ 'professional': 5
128
+ };
129
+ /**
130
+ * Tier display names
131
+ */
132
+ var TIER_NAMES = {
133
+ 'none': 'Unverified',
134
+ 'basic': 'Basic',
135
+ 'standard': 'Standard',
136
+ 'enhanced': 'Enhanced',
137
+ 'full': 'Full',
138
+ 'professional': 'Professional'
139
+ };
140
+ /**
141
+ * Tier descriptions
142
+ */
143
+ var TIER_DESCRIPTIONS = {
144
+ 'none': 'No verification completed',
145
+ 'basic': 'Email and phone verified',
146
+ 'standard': 'Government-issued ID verified',
147
+ 'enhanced': 'ID and address verified',
148
+ 'full': 'Full KYC with source of funds',
149
+ 'professional': 'Corporate/institutional verification'
150
+ };
151
+ /**
152
+ * Default tier requirements
153
+ */
154
+ var DEFAULT_TIER_REQUIREMENTS = {
155
+ none: {
156
+ tier: 'none',
157
+ displayName: TIER_NAMES.none,
158
+ description: TIER_DESCRIPTIONS.none,
159
+ documents: [],
160
+ checks: [],
161
+ information: []
162
+ },
163
+ basic: {
164
+ tier: 'basic',
165
+ displayName: TIER_NAMES.basic,
166
+ description: TIER_DESCRIPTIONS.basic,
167
+ documents: [],
168
+ checks: [
169
+ { id: 'aml_basic', name: 'AML Screening', type: 'aml', required: true }
170
+ ],
171
+ information: [
172
+ { id: 'firstName', fieldPath: 'personalInfo.firstName', displayName: 'First Name', required: true },
173
+ { id: 'lastName', fieldPath: 'personalInfo.lastName', displayName: 'Last Name', required: true },
174
+ { id: 'dob', fieldPath: 'personalInfo.dateOfBirth', displayName: 'Date of Birth', required: true },
175
+ { id: 'email', fieldPath: 'personalInfo.email', displayName: 'Email Address', required: true }
176
+ ]
177
+ },
178
+ standard: {
179
+ tier: 'standard',
180
+ displayName: TIER_NAMES.standard,
181
+ description: TIER_DESCRIPTIONS.standard,
182
+ prerequisiteTier: 'basic',
183
+ documents: [
184
+ {
185
+ id: 'identity',
186
+ name: 'Government-issued ID',
187
+ category: 'identity',
188
+ acceptedTypes: ['passport', 'national_id', 'drivers_license'],
189
+ required: true,
190
+ mustNotBeExpired: true
191
+ },
192
+ {
193
+ id: 'selfie',
194
+ name: 'Selfie Photo',
195
+ category: 'biometric',
196
+ acceptedTypes: ['selfie'],
197
+ required: true
198
+ }
199
+ ],
200
+ checks: [
201
+ { id: 'aml', name: 'AML Screening', type: 'aml', required: true },
202
+ { id: 'pep', name: 'PEP Screening', type: 'pep', required: true },
203
+ { id: 'liveness', name: 'Liveness Check', type: 'liveness', required: true },
204
+ { id: 'face_match', name: 'Face Match', type: 'face_match', required: true }
205
+ ],
206
+ information: [
207
+ { id: 'nationality', fieldPath: 'personalInfo.nationality', displayName: 'Nationality', required: true },
208
+ { id: 'residence', fieldPath: 'personalInfo.countryOfResidence', displayName: 'Country of Residence', required: true }
209
+ ]
210
+ },
211
+ enhanced: {
212
+ tier: 'enhanced',
213
+ displayName: TIER_NAMES.enhanced,
214
+ description: TIER_DESCRIPTIONS.enhanced,
215
+ prerequisiteTier: 'standard',
216
+ documents: [
217
+ {
218
+ id: 'address',
219
+ name: 'Proof of Address',
220
+ category: 'address',
221
+ acceptedTypes: ['utility_bill', 'bank_statement', 'tax_document', 'government_letter'],
222
+ required: true,
223
+ maxAgeDays: 90
224
+ }
225
+ ],
226
+ checks: [
227
+ { id: 'sanctions', name: 'Sanctions Screening', type: 'sanctions', required: true },
228
+ { id: 'address_verify', name: 'Address Verification', type: 'address_verification', required: true }
229
+ ],
230
+ information: [
231
+ { id: 'address', fieldPath: 'addresses', displayName: 'Residential Address', required: true }
232
+ ]
233
+ },
234
+ full: {
235
+ tier: 'full',
236
+ displayName: TIER_NAMES.full,
237
+ description: TIER_DESCRIPTIONS.full,
238
+ prerequisiteTier: 'enhanced',
239
+ documents: [
240
+ {
241
+ id: 'financial',
242
+ name: 'Source of Funds Documentation',
243
+ category: 'financial',
244
+ acceptedTypes: ['proof_of_income', 'tax_return', 'bank_statement', 'employment_letter'],
245
+ required: true
246
+ }
247
+ ],
248
+ checks: [],
249
+ information: [
250
+ { id: 'occupation', fieldPath: 'personalInfo.occupation', displayName: 'Occupation', required: true },
251
+ { id: 'sof', fieldPath: 'sourceOfFunds', displayName: 'Source of Funds', required: true }
252
+ ]
253
+ },
254
+ professional: {
255
+ tier: 'professional',
256
+ displayName: TIER_NAMES.professional,
257
+ description: TIER_DESCRIPTIONS.professional,
258
+ prerequisiteTier: 'enhanced',
259
+ documents: [
260
+ {
261
+ id: 'company_reg',
262
+ name: 'Company Registration Documents',
263
+ category: 'corporate',
264
+ acceptedTypes: ['company_registration', 'articles_of_incorporation'],
265
+ required: true
266
+ },
267
+ {
268
+ id: 'ubo',
269
+ name: 'Beneficial Owner Documentation',
270
+ category: 'corporate',
271
+ acceptedTypes: ['shareholder_register'],
272
+ required: true
273
+ }
274
+ ],
275
+ checks: [],
276
+ information: [
277
+ { id: 'company_name', fieldPath: 'businessInfo.companyName', displayName: 'Company Name', required: true },
278
+ { id: 'reg_number', fieldPath: 'businessInfo.registrationNumber', displayName: 'Registration Number', required: true },
279
+ { id: 'ubos', fieldPath: 'businessInfo.beneficialOwners', displayName: 'Beneficial Owners', required: true }
280
+ ]
281
+ }
282
+ };
283
+ /**
284
+ * Default tier limits (EUR)
285
+ */
286
+ var DEFAULT_TIER_LIMITS = {
287
+ none: {
288
+ currency: 'EUR',
289
+ deposit: { minAmount: 10, maxAmount: 0, dailyLimit: 0, monthlyLimit: 0 },
290
+ withdrawal: { minAmount: 10, maxAmount: 0, dailyLimit: 0, monthlyLimit: 0 }
291
+ },
292
+ basic: {
293
+ currency: 'EUR',
294
+ deposit: { minAmount: 10, maxAmount: 1000, dailyLimit: 2000, monthlyLimit: 5000 },
295
+ withdrawal: { minAmount: 10, maxAmount: 500, dailyLimit: 1000, monthlyLimit: 2500 },
296
+ transfer: { minAmount: 10, maxAmount: 500, dailyLimit: 1000, monthlyLimit: 2500 },
297
+ maxBalance: 5000
298
+ },
299
+ standard: {
300
+ currency: 'EUR',
301
+ deposit: { minAmount: 10, maxAmount: 5000, dailyLimit: 10000, monthlyLimit: 25000 },
302
+ withdrawal: { minAmount: 10, maxAmount: 2500, dailyLimit: 5000, monthlyLimit: 15000 },
303
+ transfer: { minAmount: 10, maxAmount: 2500, dailyLimit: 5000, monthlyLimit: 15000 },
304
+ maxBalance: 50000
305
+ },
306
+ enhanced: {
307
+ currency: 'EUR',
308
+ deposit: { minAmount: 10, maxAmount: 25000, dailyLimit: 50000, monthlyLimit: 150000 },
309
+ withdrawal: { minAmount: 10, maxAmount: 15000, dailyLimit: 30000, monthlyLimit: 100000 },
310
+ transfer: { minAmount: 10, maxAmount: 15000, dailyLimit: 30000, monthlyLimit: 100000 },
311
+ maxBalance: 250000
312
+ },
313
+ full: {
314
+ currency: 'EUR',
315
+ deposit: { minAmount: 10, maxAmount: 100000, dailyLimit: 250000, monthlyLimit: 1000000 },
316
+ withdrawal: { minAmount: 10, maxAmount: 50000, dailyLimit: 150000, monthlyLimit: 500000 },
317
+ transfer: { minAmount: 10, maxAmount: 50000, dailyLimit: 150000, monthlyLimit: 500000 }
318
+ },
319
+ professional: {
320
+ currency: 'EUR',
321
+ deposit: { minAmount: 100, maxAmount: 1000000, dailyLimit: 5000000, monthlyLimit: 50000000 },
322
+ withdrawal: { minAmount: 100, maxAmount: 500000, dailyLimit: 2500000, monthlyLimit: 25000000 },
323
+ transfer: { minAmount: 100, maxAmount: 500000, dailyLimit: 2500000, monthlyLimit: 25000000 }
324
+ }
325
+ };
326
+ /**
327
+ * Minimum tier required for actions
328
+ */
329
+ var DEFAULT_ACTION_TIERS = {
330
+ 'deposit': 'basic',
331
+ 'withdrawal': 'standard',
332
+ 'transfer': 'standard',
333
+ 'trade': 'standard',
334
+ 'bet': 'basic',
335
+ 'purchase': 'basic',
336
+ 'claim_bonus': 'basic',
337
+ 'crypto_withdrawal': 'enhanced',
338
+ 'high_value_transaction': 'full',
339
+ 'corporate_account': 'professional'
340
+ };
341
+ // ═══════════════════════════════════════════════════════════════════════════
342
+ // STATIC CLASS
343
+ // ═══════════════════════════════════════════════════════════════════════════
344
+ var KYCEligibility = /** @class */ (function () {
345
+ function KYCEligibility() {
346
+ }
347
+ // ─────────────────────────────────────────────────────────────────────────
348
+ // TIER UTILITIES
349
+ // ─────────────────────────────────────────────────────────────────────────
350
+ /**
351
+ * Get numeric level for a tier (for comparisons)
352
+ */
353
+ KYCEligibility.getTierLevel = function (tier) {
354
+ var _a;
355
+ return (_a = TIER_LEVELS[tier]) !== null && _a !== void 0 ? _a : 0;
356
+ };
357
+ /**
358
+ * Compare two tiers. Returns negative if a < b, 0 if equal, positive if a > b
359
+ */
360
+ KYCEligibility.compareTiers = function (a, b) {
361
+ return this.getTierLevel(a) - this.getTierLevel(b);
362
+ };
363
+ /**
364
+ * Check if tier meets minimum requirement
365
+ */
366
+ KYCEligibility.tierMeetsRequirement = function (currentTier, requiredTier) {
367
+ return this.getTierLevel(currentTier) >= this.getTierLevel(requiredTier);
368
+ };
369
+ /**
370
+ * Get display name for a tier
371
+ */
372
+ KYCEligibility.getTierDisplayName = function (tier) {
373
+ var _a;
374
+ return (_a = TIER_NAMES[tier]) !== null && _a !== void 0 ? _a : tier;
375
+ };
376
+ /**
377
+ * Get description for a tier
378
+ */
379
+ KYCEligibility.getTierDescription = function (tier) {
380
+ var _a;
381
+ return (_a = TIER_DESCRIPTIONS[tier]) !== null && _a !== void 0 ? _a : '';
382
+ };
383
+ /**
384
+ * Get next tier in the hierarchy
385
+ */
386
+ KYCEligibility.getNextTier = function (currentTier) {
387
+ var tiers = ['none', 'basic', 'standard', 'enhanced', 'full', 'professional'];
388
+ var currentIndex = tiers.indexOf(currentTier);
389
+ if (currentIndex === -1 || currentIndex >= tiers.length - 1)
390
+ return null;
391
+ return tiers[currentIndex + 1];
392
+ };
393
+ /**
394
+ * Get all tiers in order
395
+ */
396
+ KYCEligibility.getAllTiers = function () {
397
+ return ['none', 'basic', 'standard', 'enhanced', 'full', 'professional'];
398
+ };
399
+ // ─────────────────────────────────────────────────────────────────────────
400
+ // TIER REQUIREMENTS
401
+ // ─────────────────────────────────────────────────────────────────────────
402
+ /**
403
+ * Get requirements for a tier
404
+ */
405
+ KYCEligibility.getTierRequirements = function (tier) {
406
+ return DEFAULT_TIER_REQUIREMENTS[tier];
407
+ };
408
+ /**
409
+ * Get all requirements needed to reach a tier from current tier
410
+ */
411
+ KYCEligibility.getRequirementsToReachTier = function (currentTier, targetTier) {
412
+ var requirements = [];
413
+ var tiers = this.getAllTiers();
414
+ var currentIndex = tiers.indexOf(currentTier);
415
+ var targetIndex = tiers.indexOf(targetTier);
416
+ if (targetIndex <= currentIndex)
417
+ return [];
418
+ for (var i = currentIndex + 1; i <= targetIndex; i++) {
419
+ requirements.push(this.getTierRequirements(tiers[i]));
420
+ }
421
+ return requirements;
422
+ };
423
+ /**
424
+ * Check eligibility for a target tier
425
+ */
426
+ KYCEligibility.checkTierEligibility = function (targetTier, context) {
427
+ var requirements = this.getTierRequirements(targetTier);
428
+ var missingRequirements = [];
429
+ var completedRequirements = [];
430
+ this.collectTierPrerequisite(requirements, context, missingRequirements, completedRequirements);
431
+ this.collectTierDocuments(requirements, context, missingRequirements, completedRequirements);
432
+ this.collectTierVerificationChecks(requirements, context, missingRequirements, completedRequirements);
433
+ this.collectTierMinimumAge(requirements, context, missingRequirements, completedRequirements);
434
+ var total = missingRequirements.length + completedRequirements.length;
435
+ var progress = total > 0 ? Math.round((completedRequirements.length / total) * 100) : 0;
436
+ return {
437
+ currentTier: context.currentTier,
438
+ targetTier: targetTier,
439
+ eligible: missingRequirements.length === 0,
440
+ missingRequirements: missingRequirements,
441
+ completedRequirements: completedRequirements,
442
+ progress: progress
443
+ };
444
+ };
445
+ KYCEligibility.collectTierPrerequisite = function (requirements, context, missingRequirements, completedRequirements) {
446
+ if (!requirements.prerequisiteTier)
447
+ return;
448
+ if (!this.tierMeetsRequirement(context.currentTier, requirements.prerequisiteTier)) {
449
+ missingRequirements.push("Requires ".concat(this.getTierDisplayName(requirements.prerequisiteTier), " tier first"));
450
+ return;
451
+ }
452
+ completedRequirements.push("".concat(this.getTierDisplayName(requirements.prerequisiteTier), " tier"));
453
+ };
454
+ KYCEligibility.collectTierDocuments = function (requirements, context, missingRequirements, completedRequirements) {
455
+ for (var _i = 0, _a = requirements.documents; _i < _a.length; _i++) {
456
+ var doc = _a[_i];
457
+ if (!doc.required)
458
+ continue;
459
+ var hasDoc = (doc.category === 'identity' && Boolean(context.hasIdentityDoc)) ||
460
+ (doc.category === 'address' && Boolean(context.hasAddressDoc)) ||
461
+ (doc.category === 'financial' && Boolean(context.hasFinancialDoc));
462
+ if (hasDoc)
463
+ completedRequirements.push(doc.name);
464
+ else
465
+ missingRequirements.push(doc.name);
466
+ }
467
+ };
468
+ KYCEligibility.collectTierVerificationChecks = function (requirements, context, missingRequirements, completedRequirements) {
469
+ for (var _i = 0, _a = requirements.checks; _i < _a.length; _i++) {
470
+ var check = _a[_i];
471
+ if (!check.required)
472
+ continue;
473
+ var passed = (check.type === 'aml' && Boolean(context.amlCheckPassed)) ||
474
+ (check.type === 'pep' && Boolean(context.pepCheckPassed)) ||
475
+ (check.type === 'sanctions' && Boolean(context.sanctionsCheckPassed)) ||
476
+ (check.type === 'liveness' && Boolean(context.livenessCheckPassed));
477
+ if (passed)
478
+ completedRequirements.push(check.name);
479
+ else
480
+ missingRequirements.push(check.name);
481
+ }
482
+ };
483
+ KYCEligibility.collectTierMinimumAge = function (requirements, context, missingRequirements, completedRequirements) {
484
+ if (!requirements.minimumAge || context.userAge == null)
485
+ return;
486
+ if (context.userAge < requirements.minimumAge) {
487
+ missingRequirements.push("Minimum age ".concat(requirements.minimumAge));
488
+ return;
489
+ }
490
+ completedRequirements.push('Age requirement');
491
+ };
492
+ // ─────────────────────────────────────────────────────────────────────────
493
+ // PRE-VALIDATION — bulletproof client + server admission control
494
+ // ─────────────────────────────────────────────────────────────────────────
495
+ /**
496
+ * Run the full admission gate against a single input. Pure — no I/O. Same function
497
+ * intended to run on the client (before submit) and on the server (in pre-transaction
498
+ * middleware or the wallet.order handler) for defence-in-depth.
499
+ *
500
+ * Order of checks (first failure wins; same order on client and server):
501
+ *
502
+ * 1. **Account preconditions** — suspended? self-excluded?
503
+ * 2. **Wallet preconditions** — status === 'active'?
504
+ * 3. **KYC snapshot** — status approved/in_review? expiresAt > now?
505
+ * 4. **Balance** (outflows only) — wallet.balance >= amount?
506
+ * 5. **Tier limits** — delegates to `checkTransaction` (min/max per-tx, daily/monthly
507
+ * aggregate, transaction count, max-balance ceiling). Bounded `code` flows through.
508
+ *
509
+ * The client app supplies `wallet`, `account`, usage, and KYC snapshot from API
510
+ * responses it already has; the server supplies the same fields from its source of
511
+ * truth. Both sides produce identical results.
512
+ *
513
+ * Why this exists alongside `checkTransaction`: that method only handles tier-limit
514
+ * math and assumes a valid KYC snapshot. `prevalidate` is the high-level umbrella —
515
+ * checks account / wallet / status / expiry first, then delegates limits. UI code
516
+ * should always call `prevalidate`, never `checkTransaction` directly.
517
+ */
518
+ KYCEligibility.prevalidate = function (input) {
519
+ var _a, _b;
520
+ // 1. Account
521
+ var accountReject = this.preRejectAccount(input);
522
+ if (accountReject)
523
+ return accountReject;
524
+ // 2. Wallet status
525
+ var walletStatusReject = this.preRejectWalletStatus(input);
526
+ if (walletStatusReject)
527
+ return walletStatusReject;
528
+ // 3. KYC snapshot
529
+ var kycReject = this.preRejectKyc(input);
530
+ if (kycReject)
531
+ return kycReject;
532
+ // 4. Balance (outflow only)
533
+ var balanceReject = this.preRejectInsufficientBalance(input);
534
+ if (balanceReject)
535
+ return balanceReject;
536
+ // 5. Tier limits — delegate to checkTransaction. The bounded `code` set in each
537
+ // txReject* method flows back here unchanged; we just promote remaining fields
538
+ // into the KYCPrevalidationResult shape for UI consumption.
539
+ var limitsLadder = (_a = input.jurisdictionLimits) !== null && _a !== void 0 ? _a : DEFAULT_TIER_LIMITS;
540
+ var txResult = this.checkTransaction(limitsLadder, {
541
+ currentTier: input.currentTier,
542
+ kycStatus: input.kycStatus,
543
+ transactionType: input.operation,
544
+ amount: input.amount,
545
+ currency: input.currency,
546
+ usedToday: input.usedToday,
547
+ usedThisWeek: input.usedThisWeek,
548
+ usedThisMonth: input.usedThisMonth,
549
+ transactionsToday: input.transactionsToday,
550
+ transactionsThisMonth: input.transactionsThisMonth,
551
+ currentBalance: (_b = input.wallet) === null || _b === void 0 ? void 0 : _b.balance,
552
+ jurisdictionLimits: input.jurisdictionLimits
553
+ });
554
+ return __assign(__assign(__assign(__assign(__assign(__assign(__assign(__assign({ allowed: txResult.allowed }, (txResult.code ? { code: txResult.code } : {})), (txResult.reason ? { reason: txResult.reason } : {})), (txResult.details ? { details: txResult.details } : {})), (txResult.maxAllowed !== undefined ? { maxAllowed: txResult.maxAllowed } : {})), (txResult.dailyRemaining !== undefined ? { dailyRemaining: txResult.dailyRemaining } : {})), (txResult.monthlyRemaining !== undefined ? { monthlyRemaining: txResult.monthlyRemaining } : {})), (txResult.requiredTier ? { upgradeToTier: txResult.requiredTier } : {})), (txResult.upgradeMessage ? { upgradeMessage: txResult.upgradeMessage } : {}));
555
+ };
556
+ KYCEligibility.preRejectAccount = function (input) {
557
+ var _a, _b;
558
+ if ((_a = input.account) === null || _a === void 0 ? void 0 : _a.suspended) {
559
+ return {
560
+ allowed: false,
561
+ code: exports.KYC_VALIDATOR_ERRORS.AccountSuspended,
562
+ reason: 'Account is suspended'
563
+ };
564
+ }
565
+ if ((_b = input.account) === null || _b === void 0 ? void 0 : _b.selfExcludedUntil) {
566
+ var until = new Date(input.account.selfExcludedUntil);
567
+ var now = Date.now();
568
+ if (until.getTime() > now) {
569
+ return {
570
+ allowed: false,
571
+ code: exports.KYC_VALIDATOR_ERRORS.SelfExcluded,
572
+ reason: "Self-exclusion active until ".concat(until.toISOString()),
573
+ details: { until: until.toISOString() }
574
+ };
575
+ }
576
+ }
577
+ return null;
578
+ };
579
+ KYCEligibility.preRejectWalletStatus = function (input) {
580
+ var _a;
581
+ if (((_a = input.wallet) === null || _a === void 0 ? void 0 : _a.status) && input.wallet.status !== 'active') {
582
+ return {
583
+ allowed: false,
584
+ code: exports.KYC_VALIDATOR_ERRORS.WalletNotActive,
585
+ reason: "Wallet is ".concat(input.wallet.status),
586
+ details: { walletStatus: input.wallet.status }
587
+ };
588
+ }
589
+ return null;
590
+ };
591
+ KYCEligibility.preRejectKyc = function (input) {
592
+ // 'none' tier is the unverified state — the limit profile (which may be very tight)
593
+ // gates the check inside checkTransaction. Don't reject here purely on status.
594
+ if (input.currentTier !== 'none'
595
+ && input.kycStatus !== 'approved'
596
+ && input.kycStatus !== 'in_review') {
597
+ return {
598
+ allowed: false,
599
+ code: exports.KYC_VALIDATOR_ERRORS.KycStatusNotAllowed,
600
+ reason: "KYC status '".concat(input.kycStatus, "' does not allow transactions"),
601
+ details: { kycStatus: input.kycStatus }
602
+ };
603
+ }
604
+ if (input.expiresAt) {
605
+ var exp = new Date(input.expiresAt);
606
+ if (exp.getTime() < Date.now()) {
607
+ return {
608
+ allowed: false,
609
+ code: exports.KYC_VALIDATOR_ERRORS.KycExpired,
610
+ reason: 'KYC verification has expired',
611
+ details: { expiresAt: exp.toISOString() }
612
+ };
613
+ }
614
+ }
615
+ return null;
616
+ };
617
+ KYCEligibility.preRejectInsufficientBalance = function (input) {
618
+ var _a;
619
+ // Inflow operations don't consume wallet balance.
620
+ if (input.operation === 'deposit')
621
+ return null;
622
+ var balance = (_a = input.wallet) === null || _a === void 0 ? void 0 : _a.balance;
623
+ if (balance == null)
624
+ return null; // caller opted out of balance check
625
+ if (input.amount <= balance)
626
+ return null;
627
+ return {
628
+ allowed: false,
629
+ code: exports.KYC_VALIDATOR_ERRORS.InsufficientBalance,
630
+ reason: "Insufficient balance: have ".concat(balance, " ").concat(input.currency, ", need ").concat(input.amount),
631
+ details: { balance: balance, amount: input.amount, currency: input.currency },
632
+ maxAllowed: Math.max(0, balance)
633
+ };
634
+ };
635
+ // ─────────────────────────────────────────────────────────────────────────
636
+ // TRANSACTION LIMITS
637
+ // ─────────────────────────────────────────────────────────────────────────
638
+ /**
639
+ * Get limits for a tier
640
+ */
641
+ KYCEligibility.getTierLimits = function (tier, customLimits) {
642
+ var limits = customLimits !== null && customLimits !== void 0 ? customLimits : DEFAULT_TIER_LIMITS;
643
+ return limits[tier];
644
+ };
645
+ /**
646
+ * Check if a transaction is allowed
647
+ */
648
+ KYCEligibility.checkTransaction = function (limits, context) {
649
+ var _a, _b;
650
+ var tierLimits = this.resolveTransactionTierLimits(limits, context.currentTier);
651
+ if (!tierLimits) {
652
+ return {
653
+ allowed: false,
654
+ reason: 'Unknown KYC tier',
655
+ code: exports.KYC_VALIDATOR_ERRORS.UnknownKYCTier,
656
+ details: { tier: context.currentTier }
657
+ };
658
+ }
659
+ var kycStatusReject = this.txRejectInvalidKycStatus(context);
660
+ if (kycStatusReject)
661
+ return kycStatusReject;
662
+ var opResolved = this.resolveTransactionOpLimits(tierLimits, context);
663
+ if (!('opLimits' in opResolved))
664
+ return opResolved;
665
+ var opLimits = opResolved.opLimits;
666
+ var minReject = this.txRejectBelowMinAmount(context, opLimits);
667
+ if (minReject)
668
+ return minReject;
669
+ var maxReject = this.txRejectAboveMaxPerTransaction(context, opLimits);
670
+ if (maxReject)
671
+ return maxReject;
672
+ var usedToday = (_a = context.usedToday) !== null && _a !== void 0 ? _a : 0;
673
+ var dailyRemaining = opLimits.dailyLimit - usedToday;
674
+ var dailyReject = this.txRejectExceedsDailyBudget(context, opLimits, dailyRemaining);
675
+ if (dailyReject)
676
+ return dailyReject;
677
+ var usedThisMonth = (_b = context.usedThisMonth) !== null && _b !== void 0 ? _b : 0;
678
+ var monthlyRemaining = opLimits.monthlyLimit - usedThisMonth;
679
+ var monthlyReject = this.txRejectExceedsMonthlyBudget(context, opLimits, monthlyRemaining);
680
+ if (monthlyReject)
681
+ return monthlyReject;
682
+ var weeklyReject = this.txRejectExceedsWeeklyBudget(context, opLimits);
683
+ if (weeklyReject)
684
+ return weeklyReject;
685
+ var txCountReject = this.txRejectDailyTransactionCount(context, opLimits);
686
+ if (txCountReject)
687
+ return txCountReject;
688
+ var balanceReject = this.txRejectDepositOverMaxBalance(tierLimits, context);
689
+ if (balanceReject)
690
+ return balanceReject;
691
+ return this.txBuildAllowedResult(context, opLimits, dailyRemaining, monthlyRemaining);
692
+ };
693
+ KYCEligibility.resolveTransactionTierLimits = function (limits, currentTier) {
694
+ var _a;
695
+ if ('deposit' in limits)
696
+ return limits;
697
+ return (_a = limits[currentTier]) !== null && _a !== void 0 ? _a : null;
698
+ };
699
+ KYCEligibility.txRejectInvalidKycStatus = function (context) {
700
+ if (context.kycStatus && ['approved', 'in_review'].indexOf(context.kycStatus) === -1) {
701
+ return {
702
+ allowed: false,
703
+ reason: "KYC status '".concat(context.kycStatus, "' does not allow transactions"),
704
+ code: exports.KYC_VALIDATOR_ERRORS.KycStatusNotAllowed,
705
+ details: { kycStatus: context.kycStatus }
706
+ };
707
+ }
708
+ return null;
709
+ };
710
+ KYCEligibility.resolveTransactionOpLimits = function (tierLimits, context) {
711
+ var _a;
712
+ var opType = canonicalLimitKey(context.transactionType);
713
+ var opLimits = tierLimits[opType];
714
+ if (!opLimits) {
715
+ return {
716
+ allowed: false,
717
+ reason: "Transaction type '".concat(context.transactionType, "' not allowed at this tier"),
718
+ code: exports.KYC_VALIDATOR_ERRORS.TierNotPermitted,
719
+ requiredTier: (_a = this.getNextTier(context.currentTier)) !== null && _a !== void 0 ? _a : undefined
720
+ };
721
+ }
722
+ return { opLimits: opLimits };
723
+ };
724
+ KYCEligibility.txRejectBelowMinAmount = function (context, opLimits) {
725
+ if (opLimits.minAmount && context.amount < opLimits.minAmount) {
726
+ return {
727
+ allowed: false,
728
+ reason: "Minimum amount is ".concat(opLimits.minAmount, " ").concat(context.currency),
729
+ code: exports.KYC_VALIDATOR_ERRORS.AmountBelowMin,
730
+ details: { minAmount: opLimits.minAmount, amount: context.amount, currency: context.currency },
731
+ maxAllowed: opLimits.minAmount
732
+ };
733
+ }
734
+ return null;
735
+ };
736
+ KYCEligibility.txRejectAboveMaxPerTransaction = function (context, opLimits) {
737
+ if (context.amount <= opLimits.maxAmount)
738
+ return null;
739
+ var requiredTier = this.findTierForAmount(context.amount, context.transactionType, context.jurisdictionLimits);
740
+ return {
741
+ allowed: false,
742
+ reason: "Maximum amount per transaction is ".concat(opLimits.maxAmount, " ").concat(context.currency),
743
+ code: exports.KYC_VALIDATOR_ERRORS.AmountAboveMax,
744
+ details: { maxAmount: opLimits.maxAmount, amount: context.amount, currency: context.currency },
745
+ maxAllowed: opLimits.maxAmount,
746
+ requiredTier: requiredTier !== null && requiredTier !== void 0 ? requiredTier : undefined,
747
+ upgradeMessage: requiredTier
748
+ ? "Upgrade to ".concat(this.getTierDisplayName(requiredTier), " for higher limits")
749
+ : undefined
750
+ };
751
+ };
752
+ KYCEligibility.txRejectExceedsDailyBudget = function (context, opLimits, dailyRemaining) {
753
+ if (context.amount <= dailyRemaining)
754
+ return null;
755
+ return {
756
+ allowed: false,
757
+ reason: "Daily limit exceeded. Remaining today: ".concat(Math.max(0, dailyRemaining), " ").concat(context.currency),
758
+ code: exports.KYC_VALIDATOR_ERRORS.DailyLimitExceeded,
759
+ details: { dailyLimit: opLimits.dailyLimit, dailyRemaining: Math.max(0, dailyRemaining), currency: context.currency },
760
+ remaining: Math.max(0, dailyRemaining),
761
+ dailyRemaining: Math.max(0, dailyRemaining),
762
+ maxAllowed: Math.min(opLimits.maxAmount, dailyRemaining)
763
+ };
764
+ };
765
+ KYCEligibility.txRejectExceedsMonthlyBudget = function (context, opLimits, monthlyRemaining) {
766
+ if (context.amount <= monthlyRemaining)
767
+ return null;
768
+ return {
769
+ allowed: false,
770
+ reason: "Monthly limit exceeded. Remaining this month: ".concat(Math.max(0, monthlyRemaining), " ").concat(context.currency),
771
+ code: exports.KYC_VALIDATOR_ERRORS.MonthlyLimitExceeded,
772
+ details: { monthlyLimit: opLimits.monthlyLimit, monthlyRemaining: Math.max(0, monthlyRemaining), currency: context.currency },
773
+ remaining: Math.max(0, monthlyRemaining),
774
+ monthlyRemaining: Math.max(0, monthlyRemaining),
775
+ maxAllowed: Math.min(opLimits.maxAmount, monthlyRemaining)
776
+ };
777
+ };
778
+ KYCEligibility.txRejectExceedsWeeklyBudget = function (context, opLimits) {
779
+ var _a;
780
+ if (!opLimits.weeklyLimit)
781
+ return null;
782
+ var usedThisWeek = (_a = context.usedThisWeek) !== null && _a !== void 0 ? _a : 0;
783
+ var weeklyRemaining = opLimits.weeklyLimit - usedThisWeek;
784
+ if (context.amount <= weeklyRemaining)
785
+ return null;
786
+ return {
787
+ allowed: false,
788
+ reason: "Weekly limit exceeded. Remaining this week: ".concat(Math.max(0, weeklyRemaining), " ").concat(context.currency),
789
+ code: exports.KYC_VALIDATOR_ERRORS.WeeklyLimitExceeded,
790
+ details: { weeklyLimit: opLimits.weeklyLimit, weeklyRemaining: Math.max(0, weeklyRemaining), currency: context.currency },
791
+ remaining: Math.max(0, weeklyRemaining),
792
+ maxAllowed: Math.min(opLimits.maxAmount, weeklyRemaining)
793
+ };
794
+ };
795
+ KYCEligibility.txRejectDailyTransactionCount = function (context, opLimits) {
796
+ var _a;
797
+ if (!opLimits.maxDailyTransactions)
798
+ return null;
799
+ var txToday = (_a = context.transactionsToday) !== null && _a !== void 0 ? _a : 0;
800
+ if (txToday < opLimits.maxDailyTransactions)
801
+ return null;
802
+ return {
803
+ allowed: false,
804
+ reason: "Daily transaction limit reached (".concat(opLimits.maxDailyTransactions, " transactions)"),
805
+ code: exports.KYC_VALIDATOR_ERRORS.TransactionCountExceeded,
806
+ details: { maxDailyTransactions: opLimits.maxDailyTransactions, transactionsToday: txToday }
807
+ };
808
+ };
809
+ KYCEligibility.txRejectDepositOverMaxBalance = function (tierLimits, context) {
810
+ var _a;
811
+ if (context.transactionType !== 'deposit' || !tierLimits.maxBalance)
812
+ return null;
813
+ var currentBalance = (_a = context.currentBalance) !== null && _a !== void 0 ? _a : 0;
814
+ var newBalance = currentBalance + context.amount;
815
+ if (newBalance <= tierLimits.maxBalance)
816
+ return null;
817
+ var maxDeposit = tierLimits.maxBalance - currentBalance;
818
+ return {
819
+ allowed: false,
820
+ reason: "Would exceed maximum balance of ".concat(tierLimits.maxBalance, " ").concat(context.currency),
821
+ code: exports.KYC_VALIDATOR_ERRORS.MaxBalanceExceeded,
822
+ details: { maxBalance: tierLimits.maxBalance, currentBalance: currentBalance, currency: context.currency },
823
+ maxAllowed: Math.max(0, maxDeposit)
824
+ };
825
+ };
826
+ KYCEligibility.txBuildAllowedResult = function (context, opLimits, dailyRemaining, monthlyRemaining) {
827
+ return {
828
+ allowed: true,
829
+ remaining: Math.min(dailyRemaining, monthlyRemaining) - context.amount,
830
+ dailyRemaining: dailyRemaining - context.amount,
831
+ monthlyRemaining: monthlyRemaining - context.amount,
832
+ maxAllowed: Math.min(opLimits.maxAmount, dailyRemaining, monthlyRemaining)
833
+ };
834
+ };
835
+ /**
836
+ * Get remaining limits for a user
837
+ */
838
+ KYCEligibility.getRemainingLimits = function (tier, transactionType, usage, customLimits) {
839
+ var _a, _b, _c;
840
+ var tierLimits = this.getTierLimits(tier, customLimits);
841
+ var opType = canonicalLimitKey(transactionType);
842
+ var opLimits = tierLimits[opType];
843
+ if (!opLimits) {
844
+ return { daily: 0, monthly: 0, perTransaction: 0 };
845
+ }
846
+ return {
847
+ daily: Math.max(0, opLimits.dailyLimit - ((_a = usage.daily) !== null && _a !== void 0 ? _a : 0)),
848
+ weekly: opLimits.weeklyLimit
849
+ ? Math.max(0, opLimits.weeklyLimit - ((_b = usage.weekly) !== null && _b !== void 0 ? _b : 0))
850
+ : undefined,
851
+ monthly: Math.max(0, opLimits.monthlyLimit - ((_c = usage.monthly) !== null && _c !== void 0 ? _c : 0)),
852
+ perTransaction: opLimits.maxAmount
853
+ };
854
+ };
855
+ /**
856
+ * Find the minimum tier that allows a specific amount
857
+ */
858
+ KYCEligibility.findTierForAmount = function (amount, transactionType, customLimits) {
859
+ var limits = customLimits !== null && customLimits !== void 0 ? customLimits : DEFAULT_TIER_LIMITS;
860
+ var tiers = this.getAllTiers();
861
+ var opType = canonicalLimitKey(transactionType);
862
+ for (var _i = 0, tiers_1 = tiers; _i < tiers_1.length; _i++) {
863
+ var tier = tiers_1[_i];
864
+ var tierLimits = limits[tier];
865
+ var opLimits = tierLimits[opType];
866
+ if (opLimits && opLimits.maxAmount >= amount) {
867
+ return tier;
868
+ }
869
+ }
870
+ return null;
871
+ };
872
+ // ─────────────────────────────────────────────────────────────────────────
873
+ // ACTION ELIGIBILITY
874
+ // ─────────────────────────────────────────────────────────────────────────
875
+ /**
876
+ * Check if user can perform an action based on their tier
877
+ */
878
+ KYCEligibility.canPerformAction = function (action, currentTier, customActionTiers) {
879
+ var actionTiers = customActionTiers !== null && customActionTiers !== void 0 ? customActionTiers : DEFAULT_ACTION_TIERS;
880
+ var requiredTier = actionTiers[action];
881
+ if (!requiredTier) {
882
+ // Action not defined, allow by default
883
+ return { allowed: true };
884
+ }
885
+ var allowed = this.tierMeetsRequirement(currentTier, requiredTier);
886
+ return {
887
+ allowed: allowed,
888
+ reason: allowed ? undefined : "Requires ".concat(this.getTierDisplayName(requiredTier), " tier"),
889
+ requiredTier: allowed ? undefined : requiredTier
890
+ };
891
+ };
892
+ /**
893
+ * Get all actions a tier can perform
894
+ */
895
+ KYCEligibility.getAvailableActions = function (tier, customActionTiers) {
896
+ var actionTiers = customActionTiers !== null && customActionTiers !== void 0 ? customActionTiers : DEFAULT_ACTION_TIERS;
897
+ var available = [];
898
+ for (var _i = 0, _a = Object.keys(actionTiers); _i < _a.length; _i++) {
899
+ var action = _a[_i];
900
+ var requiredTier = actionTiers[action];
901
+ if (this.tierMeetsRequirement(tier, requiredTier)) {
902
+ available.push(action);
903
+ }
904
+ }
905
+ return available;
906
+ };
907
+ /**
908
+ * Get actions that require upgrade
909
+ */
910
+ KYCEligibility.getLockedActions = function (tier, customActionTiers) {
911
+ var actionTiers = customActionTiers !== null && customActionTiers !== void 0 ? customActionTiers : DEFAULT_ACTION_TIERS;
912
+ var locked = [];
913
+ for (var _i = 0, _a = Object.keys(actionTiers); _i < _a.length; _i++) {
914
+ var action = _a[_i];
915
+ var requiredTier = actionTiers[action];
916
+ if (!this.tierMeetsRequirement(tier, requiredTier)) {
917
+ locked.push({ action: action, requiredTier: requiredTier });
918
+ }
919
+ }
920
+ return locked;
921
+ };
922
+ // ─────────────────────────────────────────────────────────────────────────
923
+ // JURISDICTION RULES
924
+ // ─────────────────────────────────────────────────────────────────────────
925
+ /**
926
+ * Check if a country is blocked
927
+ */
928
+ KYCEligibility.isCountryBlocked = function (country, rules) {
929
+ return rules.blockedCountries.indexOf(country) !== -1;
930
+ };
931
+ /**
932
+ * Check if a country is high risk
933
+ */
934
+ KYCEligibility.isCountryHighRisk = function (country, rules) {
935
+ return rules.highRiskCountries.indexOf(country) !== -1;
936
+ };
937
+ /**
938
+ * Check age eligibility for a jurisdiction
939
+ */
940
+ KYCEligibility.checkAgeEligibility = function (userAge, rules) {
941
+ if (userAge < rules.minimumAge) {
942
+ return {
943
+ eligible: false,
944
+ reason: "Minimum age is ".concat(rules.minimumAge, " years")
945
+ };
946
+ }
947
+ return { eligible: true };
948
+ };
949
+ /**
950
+ * Check nationality eligibility
951
+ */
952
+ KYCEligibility.checkNationalityEligibility = function (nationality, rules) {
953
+ var _a;
954
+ if (((_a = rules.restrictedNationalities) === null || _a === void 0 ? void 0 : _a.indexOf(nationality)) !== -1) {
955
+ return {
956
+ eligible: false,
957
+ reason: "Nationality '".concat(nationality, "' is restricted")
958
+ };
959
+ }
960
+ return { eligible: true };
961
+ };
962
+ // ─────────────────────────────────────────────────────────────────────────
963
+ // DOCUMENT VALIDATION (Client-side)
964
+ // ─────────────────────────────────────────────────────────────────────────
965
+ /**
966
+ * Check if a document is expired
967
+ */
968
+ KYCEligibility.isDocumentExpired = function (expiryDate, currentDate) {
969
+ var now = currentDate !== null && currentDate !== void 0 ? currentDate : new Date();
970
+ return new Date(expiryDate) < now;
971
+ };
972
+ /**
973
+ * Check if a document is too old (e.g., utility bill older than 3 months)
974
+ */
975
+ KYCEligibility.isDocumentTooOld = function (issuedDate, maxAgeDays, currentDate) {
976
+ var now = currentDate !== null && currentDate !== void 0 ? currentDate : new Date();
977
+ var ageMs = now.getTime() - new Date(issuedDate).getTime();
978
+ var ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
979
+ return ageDays > maxAgeDays;
980
+ };
981
+ /**
982
+ * Validate document file (client-side pre-validation)
983
+ */
984
+ KYCEligibility.validateDocumentFile = function (file, options) {
985
+ var _a;
986
+ if (options === void 0) { options = {}; }
987
+ var _b = options.maxSizeMB, maxSizeMB = _b === void 0 ? 10 : _b, _c = options.allowedTypes, allowedTypes = _c === void 0 ? ['image/jpeg', 'image/png', 'application/pdf'] : _c, _d = options.allowedExtensions, allowedExtensions = _d === void 0 ? ['.jpg', '.jpeg', '.png', '.pdf'] : _d;
988
+ // Check file size
989
+ var sizeMB = file.size / (1024 * 1024);
990
+ if (sizeMB > maxSizeMB) {
991
+ return {
992
+ valid: false,
993
+ reason: "File too large. Maximum size is ".concat(maxSizeMB, "MB"),
994
+ code: exports.KYC_VALIDATOR_ERRORS.FileTooLarge,
995
+ details: { maxSizeMB: maxSizeMB, sizeMB: sizeMB }
996
+ };
997
+ }
998
+ // Check mime type
999
+ if (allowedTypes.indexOf(file.type) === -1) {
1000
+ return {
1001
+ valid: false,
1002
+ reason: "File type '".concat(file.type, "' not allowed"),
1003
+ code: exports.KYC_VALIDATOR_ERRORS.FileTypeNotAllowed,
1004
+ details: { fileType: file.type, allowedTypes: allowedTypes }
1005
+ };
1006
+ }
1007
+ // Check extension
1008
+ var ext = '.' + ((_a = file.name.split('.').pop()) === null || _a === void 0 ? void 0 : _a.toLowerCase());
1009
+ if (allowedExtensions.indexOf(ext) === -1) {
1010
+ return {
1011
+ valid: false,
1012
+ reason: "File extension '".concat(ext, "' not allowed"),
1013
+ code: exports.KYC_VALIDATOR_ERRORS.FileExtensionNotAllowed,
1014
+ details: { extension: ext, allowedExtensions: allowedExtensions }
1015
+ };
1016
+ }
1017
+ return { valid: true };
1018
+ };
1019
+ // ─────────────────────────────────────────────────────────────────────────
1020
+ // RISK LEVEL UTILITIES
1021
+ // ─────────────────────────────────────────────────────────────────────────
1022
+ /**
1023
+ * Calculate risk level from score
1024
+ */
1025
+ KYCEligibility.getRiskLevelFromScore = function (score, thresholds) {
1026
+ if (thresholds === void 0) { thresholds = { low: 30, medium: 50, high: 70 }; }
1027
+ if (score >= thresholds.high)
1028
+ return 'critical';
1029
+ if (score >= thresholds.medium)
1030
+ return 'high';
1031
+ if (score >= thresholds.low)
1032
+ return 'medium';
1033
+ return 'low';
1034
+ };
1035
+ /**
1036
+ * Check if enhanced due diligence is required
1037
+ */
1038
+ KYCEligibility.requiresEnhancedDueDiligence = function (context) {
1039
+ // PEP always requires EDD
1040
+ if (context.isPEP)
1041
+ return true;
1042
+ // High/critical risk requires EDD
1043
+ if (context.riskLevel === 'high' || context.riskLevel === 'critical')
1044
+ return true;
1045
+ // High risk score requires EDD
1046
+ if (context.riskScore && context.riskScore >= 70)
1047
+ return true;
1048
+ return false;
1049
+ };
1050
+ return KYCEligibility;
1051
+ }());
1052
+ exports.KYCEligibility = KYCEligibility;
1053
+ // ═══════════════════════════════════════════════════════════════════════════
1054
+ // CONVENIENCE EXPORTS
1055
+ // ═══════════════════════════════════════════════════════════════════════════
1056
+ exports.checkKYCTransaction = KYCEligibility.checkTransaction.bind(KYCEligibility);
1057
+ exports.getTierRequirements = KYCEligibility.getTierRequirements.bind(KYCEligibility);
1058
+ exports.checkTierEligibility = KYCEligibility.checkTierEligibility.bind(KYCEligibility);
1059
+ exports.canPerformAction = KYCEligibility.canPerformAction.bind(KYCEligibility);
1060
+ exports.tierMeetsRequirement = KYCEligibility.tierMeetsRequirement.bind(KYCEligibility);
1061
+ exports.getRemainingLimits = KYCEligibility.getRemainingLimits.bind(KYCEligibility);
1062
+ /**
1063
+ * Top-level pre-validation helper — see {@link KYCEligibility.prevalidate}.
1064
+ * Bulletproof client + server admission control. Pure function; no I/O.
1065
+ */
1066
+ exports.prevalidateKYCTransaction = KYCEligibility.prevalidate.bind(KYCEligibility);