@alviere/core 0.8.1
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/LICENSE +201 -0
- package/dist/entities/constants/environment.d.ts +29 -0
- package/dist/entities/constants/environment.d.ts.map +1 -0
- package/dist/entities/constants/error-codes.d.ts +51 -0
- package/dist/entities/constants/error-codes.d.ts.map +1 -0
- package/dist/entities/errors/alcore-api.error.d.ts +19 -0
- package/dist/entities/errors/alcore-api.error.d.ts.map +1 -0
- package/dist/entities/interfaces/cryptor.interface.d.ts +10 -0
- package/dist/entities/interfaces/cryptor.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/accounts.interface.d.ts +48 -0
- package/dist/entities/interfaces/gateways/accounts.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/auth.interface.d.ts +10 -0
- package/dist/entities/interfaces/gateways/auth.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/card-issuance.interface.d.ts +6 -0
- package/dist/entities/interfaces/gateways/card-issuance.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/payment-instruments.interface.d.ts +14 -0
- package/dist/entities/interfaces/gateways/payment-instruments.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/payments.interface.d.ts +11 -0
- package/dist/entities/interfaces/gateways/payments.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/transactions.interface.d.ts +32 -0
- package/dist/entities/interfaces/gateways/transactions.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/gateways/wallets.interface.d.ts +11 -0
- package/dist/entities/interfaces/gateways/wallets.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/logger.interface.d.ts +13 -0
- package/dist/entities/interfaces/logger.interface.d.ts.map +1 -0
- package/dist/entities/interfaces/validator.interface.d.ts +25 -0
- package/dist/entities/interfaces/validator.interface.d.ts.map +1 -0
- package/dist/entities/requests/_alcore.d.ts +9 -0
- package/dist/entities/requests/_alcore.d.ts.map +1 -0
- package/dist/entities/requests/accounts/accounts.d.ts +110 -0
- package/dist/entities/requests/accounts/accounts.d.ts.map +1 -0
- package/dist/entities/requests/accounts/addresses.d.ts +3 -0
- package/dist/entities/requests/accounts/addresses.d.ts.map +1 -0
- package/dist/entities/requests/accounts/dossiers.d.ts +36 -0
- package/dist/entities/requests/accounts/dossiers.d.ts.map +1 -0
- package/dist/entities/requests/auth/auth.d.ts +41 -0
- package/dist/entities/requests/auth/auth.d.ts.map +1 -0
- package/dist/entities/requests/card-issuance/card-issuance.d.ts +14 -0
- package/dist/entities/requests/card-issuance/card-issuance.d.ts.map +1 -0
- package/dist/entities/requests/payments/bank-accounts.d.ts +47 -0
- package/dist/entities/requests/payments/bank-accounts.d.ts.map +1 -0
- package/dist/entities/requests/payments/index.d.ts +4 -0
- package/dist/entities/requests/payments/index.d.ts.map +1 -0
- package/dist/entities/requests/payments/payment-instruments.d.ts +68 -0
- package/dist/entities/requests/payments/payment-instruments.d.ts.map +1 -0
- package/dist/entities/requests/payments/payments.d.ts +23 -0
- package/dist/entities/requests/payments/payments.d.ts.map +1 -0
- package/dist/entities/requests/transactions/transactions.d.ts +43 -0
- package/dist/entities/requests/transactions/transactions.d.ts.map +1 -0
- package/dist/entities/requests/utility.d.ts +61 -0
- package/dist/entities/requests/utility.d.ts.map +1 -0
- package/dist/entities/requests/wallets/wallets.d.ts +105 -0
- package/dist/entities/requests/wallets/wallets.d.ts.map +1 -0
- package/dist/entities/responses/accounts/accounts.d.ts +40 -0
- package/dist/entities/responses/accounts/accounts.d.ts.map +1 -0
- package/dist/entities/responses/accounts/addresses.d.ts +9 -0
- package/dist/entities/responses/accounts/addresses.d.ts.map +1 -0
- package/dist/entities/responses/accounts/dossiers.d.ts +26 -0
- package/dist/entities/responses/accounts/dossiers.d.ts.map +1 -0
- package/dist/entities/responses/alcore.d.ts +8 -0
- package/dist/entities/responses/alcore.d.ts.map +1 -0
- package/dist/entities/responses/auth/auth.d.ts +26 -0
- package/dist/entities/responses/auth/auth.d.ts.map +1 -0
- package/dist/entities/responses/card-issuance/set-pin.d.ts +6 -0
- package/dist/entities/responses/card-issuance/set-pin.d.ts.map +1 -0
- package/dist/entities/responses/payments/add_card.d.ts +27 -0
- package/dist/entities/responses/payments/add_card.d.ts.map +1 -0
- package/dist/entities/responses/payments/bank_accounts.d.ts +61 -0
- package/dist/entities/responses/payments/bank_accounts.d.ts.map +1 -0
- package/dist/entities/responses/payments/index.d.ts +4 -0
- package/dist/entities/responses/payments/index.d.ts.map +1 -0
- package/dist/entities/responses/payments/payment-instruments.d.ts +73 -0
- package/dist/entities/responses/payments/payment-instruments.d.ts.map +1 -0
- package/dist/entities/responses/transactions/transactions.d.ts +93 -0
- package/dist/entities/responses/transactions/transactions.d.ts.map +1 -0
- package/dist/entities/responses/wallets/wallets.d.ts +78 -0
- package/dist/entities/responses/wallets/wallets.d.ts.map +1 -0
- package/dist/entities/results/card-issuance/set-pin.d.ts +4 -0
- package/dist/entities/results/card-issuance/set-pin.d.ts.map +1 -0
- package/dist/entities/results/payments/add_bank_account.d.ts +47 -0
- package/dist/entities/results/payments/add_bank_account.d.ts.map +1 -0
- package/dist/entities/results/payments/add_card.d.ts +29 -0
- package/dist/entities/results/payments/add_card.d.ts.map +1 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2419 -0
- package/dist/index.mjs.map +1 -0
- package/dist/infrastructure/gateways/accounts.gateway.d.ts +30 -0
- package/dist/infrastructure/gateways/accounts.gateway.d.ts.map +1 -0
- package/dist/infrastructure/gateways/alcore-base.gateway.d.ts +45 -0
- package/dist/infrastructure/gateways/alcore-base.gateway.d.ts.map +1 -0
- package/dist/infrastructure/gateways/auth.gateway.d.ts +25 -0
- package/dist/infrastructure/gateways/auth.gateway.d.ts.map +1 -0
- package/dist/infrastructure/gateways/card-issuance.gateway.d.ts +23 -0
- package/dist/infrastructure/gateways/card-issuance.gateway.d.ts.map +1 -0
- package/dist/infrastructure/gateways/payment-instruments.gateway.d.ts +15 -0
- package/dist/infrastructure/gateways/payment-instruments.gateway.d.ts.map +1 -0
- package/dist/infrastructure/gateways/payments.gateway.d.ts +24 -0
- package/dist/infrastructure/gateways/payments.gateway.d.ts.map +1 -0
- package/dist/infrastructure/gateways/wallets.gateway.d.ts +12 -0
- package/dist/infrastructure/gateways/wallets.gateway.d.ts.map +1 -0
- package/dist/infrastructure/logging/logger.service.d.ts +52 -0
- package/dist/infrastructure/logging/logger.service.d.ts.map +1 -0
- package/dist/infrastructure/security/cryptor.service.d.ts +68 -0
- package/dist/infrastructure/security/cryptor.service.d.ts.map +1 -0
- package/dist/infrastructure/security/validator.service.d.ts +128 -0
- package/dist/infrastructure/security/validator.service.d.ts.map +1 -0
- package/dist/plugins/field-plugin-registry.d.ts +102 -0
- package/dist/plugins/field-plugin-registry.d.ts.map +1 -0
- package/dist/plugins/field-plugin.interface.d.ts +123 -0
- package/dist/plugins/field-plugin.interface.d.ts.map +1 -0
- package/dist/plugins/fields/card-number.plugin.d.ts +80 -0
- package/dist/plugins/fields/card-number.plugin.d.ts.map +1 -0
- package/dist/plugins/fields/cvv.plugin.d.ts +70 -0
- package/dist/plugins/fields/cvv.plugin.d.ts.map +1 -0
- package/dist/plugins/fields/expiry-date.plugin.d.ts +54 -0
- package/dist/plugins/fields/expiry-date.plugin.d.ts.map +1 -0
- package/dist/plugins/fields/text.plugin.d.ts +79 -0
- package/dist/plugins/fields/text.plugin.d.ts.map +1 -0
- package/dist/services/accounts-api.service.d.ts +28 -0
- package/dist/services/accounts-api.service.d.ts.map +1 -0
- package/dist/services/payment-instruments-api.service.d.ts +13 -0
- package/dist/services/payment-instruments-api.service.d.ts.map +1 -0
- package/dist/services/payment-processor.service.d.ts +43 -0
- package/dist/services/payment-processor.service.d.ts.map +1 -0
- package/dist/services/wallets-api.service.d.ts +10 -0
- package/dist/services/wallets-api.service.d.ts.map +1 -0
- package/package.json +54 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2419 @@
|
|
|
1
|
+
import { pki } from "node-forge";
|
|
2
|
+
class Logger {
|
|
3
|
+
/**
|
|
4
|
+
* Logger constructor.
|
|
5
|
+
*
|
|
6
|
+
* @param isDebug This is what enables/disables our output messages
|
|
7
|
+
*/
|
|
8
|
+
constructor(isDebug) {
|
|
9
|
+
this.logger = "CORE_LOGGER";
|
|
10
|
+
this.isDebug = isDebug;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* This is the method to output a debug message to console.
|
|
14
|
+
*
|
|
15
|
+
* @param msg The message to output to console
|
|
16
|
+
*/
|
|
17
|
+
debug(msg) {
|
|
18
|
+
if (this.isDebug) {
|
|
19
|
+
if (typeof console.debug !== "undefined") {
|
|
20
|
+
console.debug(msg);
|
|
21
|
+
} else {
|
|
22
|
+
console.log(msg);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* This is the method to output a warning message to console.
|
|
28
|
+
*
|
|
29
|
+
* @param msg The message to output to console
|
|
30
|
+
*/
|
|
31
|
+
warning(msg) {
|
|
32
|
+
if (this.isDebug) {
|
|
33
|
+
if (typeof console.warn !== "undefined") {
|
|
34
|
+
console.warn(msg);
|
|
35
|
+
} else {
|
|
36
|
+
console.log(msg);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Alias for warning() for consistency with console API
|
|
42
|
+
*
|
|
43
|
+
* @param msg The message to output to console
|
|
44
|
+
*/
|
|
45
|
+
warn(msg) {
|
|
46
|
+
this.warning(msg);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* This is the method to output an info message to console.
|
|
50
|
+
*
|
|
51
|
+
* @param msg The message to output to console
|
|
52
|
+
*/
|
|
53
|
+
info(msg) {
|
|
54
|
+
if (this.isDebug) {
|
|
55
|
+
if (typeof console.info !== "undefined") {
|
|
56
|
+
console.info(msg);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(msg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* This is the method to output an error message to console.
|
|
64
|
+
*
|
|
65
|
+
* @param msg The message to output to console
|
|
66
|
+
*/
|
|
67
|
+
error(msg) {
|
|
68
|
+
if (this.isDebug) {
|
|
69
|
+
if (typeof console.error !== "undefined") {
|
|
70
|
+
console.error(msg);
|
|
71
|
+
} else {
|
|
72
|
+
console.log(msg);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if debug mode is enabled
|
|
78
|
+
*/
|
|
79
|
+
isDebugEnabled() {
|
|
80
|
+
return this.isDebug;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const DEFAULT_CONFIG = {
|
|
84
|
+
domain: "https://api.dev.alviere.com",
|
|
85
|
+
access_token: "",
|
|
86
|
+
certificate: {
|
|
87
|
+
id: "",
|
|
88
|
+
public_key: ""
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
let runtimeConfig = { ...DEFAULT_CONFIG };
|
|
92
|
+
function setRuntimeConfig(config) {
|
|
93
|
+
runtimeConfig = { ...runtimeConfig, ...config };
|
|
94
|
+
}
|
|
95
|
+
function getRuntimeConfig() {
|
|
96
|
+
return runtimeConfig;
|
|
97
|
+
}
|
|
98
|
+
function getEnvVar(key) {
|
|
99
|
+
if (typeof window !== "undefined" && window.VITE_DEBUG) {
|
|
100
|
+
const viteKey = `VITE_${key}`;
|
|
101
|
+
return window[viteKey];
|
|
102
|
+
}
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
function initializeFromEnvironment() {
|
|
106
|
+
if (runtimeConfig.domain && runtimeConfig.certificate) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const envConfig = {};
|
|
110
|
+
const envDomain = getEnvVar("ALVIERE_DOMAIN");
|
|
111
|
+
const envCertificate = getEnvVar("CERTIFICATE");
|
|
112
|
+
if (envDomain) envConfig.domain = envDomain;
|
|
113
|
+
if (envCertificate) {
|
|
114
|
+
try {
|
|
115
|
+
const cert = JSON.parse(envCertificate);
|
|
116
|
+
if (cert && typeof cert.id === "string" && typeof cert.public_key === "string") {
|
|
117
|
+
envConfig.certificate = { id: cert.id, public_key: cert.public_key };
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (envConfig.domain || envConfig.certificate) {
|
|
123
|
+
setRuntimeConfig(envConfig);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const getALVIERE_DOMAIN = () => {
|
|
127
|
+
if (!runtimeConfig.domain) {
|
|
128
|
+
initializeFromEnvironment();
|
|
129
|
+
}
|
|
130
|
+
return runtimeConfig.domain;
|
|
131
|
+
};
|
|
132
|
+
const getRSA_PUB_KEY = () => {
|
|
133
|
+
if (!runtimeConfig.certificate) {
|
|
134
|
+
initializeFromEnvironment();
|
|
135
|
+
}
|
|
136
|
+
return runtimeConfig.certificate.public_key;
|
|
137
|
+
};
|
|
138
|
+
const getCERTIFICATE_ID = () => {
|
|
139
|
+
if (!runtimeConfig.certificate) {
|
|
140
|
+
initializeFromEnvironment();
|
|
141
|
+
}
|
|
142
|
+
return runtimeConfig.certificate.id;
|
|
143
|
+
};
|
|
144
|
+
const ALVIERE_DOMAIN = getALVIERE_DOMAIN();
|
|
145
|
+
const RSA_PUB_KEY = getRSA_PUB_KEY();
|
|
146
|
+
const CERTIFICATE_ID = getCERTIFICATE_ID();
|
|
147
|
+
class Cryptor {
|
|
148
|
+
/**
|
|
149
|
+
*
|
|
150
|
+
*/
|
|
151
|
+
constructor(logger) {
|
|
152
|
+
this.logger = logger;
|
|
153
|
+
this.rsa_key = getRSA_PUB_KEY();
|
|
154
|
+
this.key = this.generate_key();
|
|
155
|
+
this.nonce = this.generate_nonce();
|
|
156
|
+
this.logger?.debug("🔐 Cryptor initialized");
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
*
|
|
160
|
+
* @param data
|
|
161
|
+
* @returns
|
|
162
|
+
*/
|
|
163
|
+
async encrypt(data) {
|
|
164
|
+
const encoded = new TextEncoder().encode(JSON.stringify(data));
|
|
165
|
+
const alg = { name: "AES-GCM", iv: this.nonce };
|
|
166
|
+
const crypto_key = await this.import_key();
|
|
167
|
+
const cipher = await crypto.subtle.encrypt(alg, crypto_key, encoded);
|
|
168
|
+
const cipherStr = this.bufferToStr(cipher);
|
|
169
|
+
const cipherB64 = btoa(cipherStr);
|
|
170
|
+
const keyStr = this.bufferToStr(this.key);
|
|
171
|
+
const keyB64 = btoa(keyStr);
|
|
172
|
+
const nonceStr = this.bufferToStr(this.nonce);
|
|
173
|
+
const nonceB64 = btoa(nonceStr);
|
|
174
|
+
this.logger?.debug(`🔐 RSA_KEY: ${this.rsa_key.substring(0, 50)}...`);
|
|
175
|
+
const publicKey = pki.publicKeyFromPem(this.rsa_key);
|
|
176
|
+
const keyNonceCipher = publicKey.encrypt(keyB64 + "::" + nonceB64);
|
|
177
|
+
const keyNonceB64 = btoa(keyNonceCipher);
|
|
178
|
+
const encrypted_request = {
|
|
179
|
+
p: cipherB64,
|
|
180
|
+
// base64(AES-GCM(<sdk_envelope>))
|
|
181
|
+
k: keyNonceB64,
|
|
182
|
+
// base64(RSA(base64(SDK_KEY)::base64(<sdk_nonce>)))
|
|
183
|
+
i: getCERTIFICATE_ID()
|
|
184
|
+
// certificate id - configurable via environment variable
|
|
185
|
+
};
|
|
186
|
+
this.logger?.debug(
|
|
187
|
+
`🔐 ENCRYPTED_REQUEST: p=${encrypted_request.p.substring(0, 20)}..., i=${encrypted_request.i}`
|
|
188
|
+
);
|
|
189
|
+
return encrypted_request;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
*
|
|
193
|
+
* @param data
|
|
194
|
+
* @returns
|
|
195
|
+
*/
|
|
196
|
+
async decrypt(data) {
|
|
197
|
+
const decrypted = await crypto.subtle.decrypt(
|
|
198
|
+
{ name: "AES-GCM", iv: this.nonce },
|
|
199
|
+
await this.import_key(),
|
|
200
|
+
Uint8Array.from(atob(data.p), (c) => c.charCodeAt(0))
|
|
201
|
+
);
|
|
202
|
+
const decryptedText = new TextDecoder().decode(decrypted);
|
|
203
|
+
this.logger?.debug(`🔐 DECRYPTED: ${decryptedText}`);
|
|
204
|
+
return JSON.parse(decryptedText);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
*
|
|
208
|
+
* @returns
|
|
209
|
+
*/
|
|
210
|
+
generate_key() {
|
|
211
|
+
return crypto.getRandomValues(new Uint8Array(32));
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
*
|
|
215
|
+
* @returns
|
|
216
|
+
*/
|
|
217
|
+
generate_nonce() {
|
|
218
|
+
return crypto.getRandomValues(new Uint8Array(12));
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
*
|
|
222
|
+
* @returns
|
|
223
|
+
*/
|
|
224
|
+
async import_key() {
|
|
225
|
+
return await crypto.subtle.importKey("raw", this.key, { name: "AES-GCM" }, true, [
|
|
226
|
+
"encrypt",
|
|
227
|
+
"decrypt"
|
|
228
|
+
]);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
*
|
|
232
|
+
* @param data
|
|
233
|
+
* @returns
|
|
234
|
+
*/
|
|
235
|
+
async rsa_encrypt(data) {
|
|
236
|
+
this.logger?.debug(`🔐 RSA_ENCRYPT: Data to encrypt: ${data.substring(0, 50)}...`);
|
|
237
|
+
this.logger?.debug(`🔐 RSA_KEY: ${this.rsa_key.substring(0, 50)}...`);
|
|
238
|
+
const publicKey = pki.publicKeyFromPem(this.rsa_key);
|
|
239
|
+
return btoa(publicKey.encrypt(data));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
*
|
|
243
|
+
* @param buf
|
|
244
|
+
*/
|
|
245
|
+
bufferToStr(buf) {
|
|
246
|
+
const cb = Array.from(new Uint8Array(buf));
|
|
247
|
+
const cs = cb.map((byte) => String.fromCharCode(byte)).join("");
|
|
248
|
+
return cs;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
class Validator {
|
|
252
|
+
/**
|
|
253
|
+
*
|
|
254
|
+
* @param logger
|
|
255
|
+
*/
|
|
256
|
+
constructor(logger) {
|
|
257
|
+
this.logger = logger;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
*
|
|
261
|
+
* @param name
|
|
262
|
+
*/
|
|
263
|
+
credit_card_name(name) {
|
|
264
|
+
this.logger.debug(`🔐 CREDIT_CARD_NAME: ${name}`);
|
|
265
|
+
if (!name || typeof name !== "string") {
|
|
266
|
+
return "Card holder name cannot be empty.";
|
|
267
|
+
}
|
|
268
|
+
if (name.trim().length === 0) {
|
|
269
|
+
return "Card holder name cannot be empty.";
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
*
|
|
275
|
+
* @param number
|
|
276
|
+
*/
|
|
277
|
+
credit_card_number(number) {
|
|
278
|
+
if (!number || typeof number !== "string") {
|
|
279
|
+
return "Please enter a valid card number.";
|
|
280
|
+
}
|
|
281
|
+
const visaRegEx = /^(?:4[0-9]{12}(?:[0-9]{3})?)$/;
|
|
282
|
+
const mastercardRegEx = /^(?:5[1-5][0-9]{14})$/;
|
|
283
|
+
const amexpRegEx = /^(?:3[47][0-9]{13})$/;
|
|
284
|
+
const discovRegEx = /^(?:6(?:011|5[0-9][0-9])[0-9]{12})$/;
|
|
285
|
+
if (visaRegEx.test(number.replace(/\s/g, ""))) {
|
|
286
|
+
return null;
|
|
287
|
+
} else if (mastercardRegEx.test(number.replace(/\s/g, ""))) {
|
|
288
|
+
return null;
|
|
289
|
+
} else if (amexpRegEx.test(number.replace(/\s/g, ""))) {
|
|
290
|
+
return null;
|
|
291
|
+
} else if (discovRegEx.test(number.replace(/\s/g, ""))) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
this.logger.debug("🔐 card number is not a recognized brand");
|
|
295
|
+
return "Please enter a valid card number.";
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
*
|
|
299
|
+
* @param expiry
|
|
300
|
+
*/
|
|
301
|
+
credit_card_expiry_date(expiry) {
|
|
302
|
+
if (!expiry || typeof expiry !== "string") {
|
|
303
|
+
return "Expiry date cannot be empty.";
|
|
304
|
+
}
|
|
305
|
+
if (expiry.trim().length === 0) {
|
|
306
|
+
return "Expiry date cannot be empty.";
|
|
307
|
+
}
|
|
308
|
+
const expiryRegEx = /^(0[1-9]|1[0-2])\/?([0-9]{4}|[0-9]{2})$/;
|
|
309
|
+
if (!expiryRegEx.test(expiry)) {
|
|
310
|
+
return "Please enter a valid expiry date.";
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
*
|
|
316
|
+
* @param code
|
|
317
|
+
*/
|
|
318
|
+
credit_card_sec_code(code) {
|
|
319
|
+
if (!code || typeof code !== "string") {
|
|
320
|
+
return "Security code cannot be empty.";
|
|
321
|
+
}
|
|
322
|
+
if (code.trim().length === 0) {
|
|
323
|
+
return "Security code cannot be empty.";
|
|
324
|
+
}
|
|
325
|
+
const secCodeRegEx = /^[0-9]{3,4}$/;
|
|
326
|
+
if (!secCodeRegEx.test(code)) {
|
|
327
|
+
return "Please enter a valid security code.";
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
*
|
|
333
|
+
* @param zip
|
|
334
|
+
*/
|
|
335
|
+
credit_card_zip_code(zip) {
|
|
336
|
+
if (!zip || typeof zip !== "string") {
|
|
337
|
+
return "Zip code cannot be empty.";
|
|
338
|
+
}
|
|
339
|
+
if (zip.trim().length === 0) {
|
|
340
|
+
return "Zip code cannot be empty.";
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
*
|
|
346
|
+
* @param pin
|
|
347
|
+
*/
|
|
348
|
+
credit_card_pin(pin) {
|
|
349
|
+
if (!pin || typeof pin !== "string") {
|
|
350
|
+
return "PIN cannot be empty.";
|
|
351
|
+
}
|
|
352
|
+
if (pin.trim().length === 0) {
|
|
353
|
+
return "PIN cannot be empty.";
|
|
354
|
+
}
|
|
355
|
+
const pinRegEx = /^[0-9]{4}$/;
|
|
356
|
+
if (!pinRegEx.test(pin)) {
|
|
357
|
+
return "Please enter a valid PIN.";
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
*
|
|
363
|
+
* @param uuid
|
|
364
|
+
*/
|
|
365
|
+
is_valid_uuid(uuid) {
|
|
366
|
+
if (!uuid || typeof uuid !== "string") {
|
|
367
|
+
return "UUID cannot be empty.";
|
|
368
|
+
}
|
|
369
|
+
if (uuid.trim().length === 0) {
|
|
370
|
+
return "UUID cannot be empty.";
|
|
371
|
+
}
|
|
372
|
+
const uuidRegEx = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
373
|
+
if (!uuidRegEx.test(uuid)) {
|
|
374
|
+
return "Please enter a valid UUID.";
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Validates that a field is not empty or null
|
|
380
|
+
* @param value - The value to validate
|
|
381
|
+
* @param fieldName - Optional field name for error message
|
|
382
|
+
*/
|
|
383
|
+
required(value, fieldName) {
|
|
384
|
+
if (value === null || value === void 0 || typeof value === "string" && value.trim().length === 0) {
|
|
385
|
+
return fieldName ? `${fieldName} is required.` : "This field is required.";
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Validates email address format
|
|
391
|
+
* @param email - The email to validate
|
|
392
|
+
*/
|
|
393
|
+
email(email) {
|
|
394
|
+
if (!email || typeof email !== "string") {
|
|
395
|
+
return "Please enter a valid email address.";
|
|
396
|
+
}
|
|
397
|
+
const trimmed = email.trim();
|
|
398
|
+
if (trimmed.includes("..")) {
|
|
399
|
+
return "Please enter a valid email address.";
|
|
400
|
+
}
|
|
401
|
+
const emailRegEx = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
|
402
|
+
if (!emailRegEx.test(trimmed)) {
|
|
403
|
+
return "Please enter a valid email address.";
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Validates phone number format
|
|
409
|
+
* @param phone - The phone number to validate
|
|
410
|
+
*/
|
|
411
|
+
phone(phone) {
|
|
412
|
+
if (!phone || typeof phone !== "string") {
|
|
413
|
+
return "Please enter a valid phone number.";
|
|
414
|
+
}
|
|
415
|
+
if (!phone.startsWith("+")) {
|
|
416
|
+
return "Phone number must start with country code (e.g., +1)";
|
|
417
|
+
}
|
|
418
|
+
const afterPlus = phone.substring(1);
|
|
419
|
+
const countryCodeMatch = afterPlus.match(/^(\d{1,3})/);
|
|
420
|
+
if (!countryCodeMatch) {
|
|
421
|
+
return "Invalid country code format";
|
|
422
|
+
}
|
|
423
|
+
const countryCode = countryCodeMatch[1];
|
|
424
|
+
const phoneNumber = afterPlus.substring(countryCode.length);
|
|
425
|
+
if (phoneNumber.length < 7) {
|
|
426
|
+
return "Phone number too short";
|
|
427
|
+
}
|
|
428
|
+
const totalDigits = countryCode.length + phoneNumber.length;
|
|
429
|
+
if (totalDigits < 10 || totalDigits > 15) {
|
|
430
|
+
return "Phone number must be between 10 and 15 digits total";
|
|
431
|
+
}
|
|
432
|
+
return null;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Validates date format and calendar validity
|
|
436
|
+
* @param date - The date string to validate
|
|
437
|
+
* @param format - The expected date format (default: 'YYYY-MM-DD')
|
|
438
|
+
*/
|
|
439
|
+
date(date, format = "YYYY-MM-DD") {
|
|
440
|
+
if (!date || typeof date !== "string") {
|
|
441
|
+
return "Please enter a valid date.";
|
|
442
|
+
}
|
|
443
|
+
const dateRegex = /^([0-9]{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/;
|
|
444
|
+
if (!dateRegex.test(date)) {
|
|
445
|
+
return "Please enter a valid date in YYYY-MM-DD format";
|
|
446
|
+
}
|
|
447
|
+
const [, yearStr, monthStr, dayStr] = date.match(dateRegex) || [];
|
|
448
|
+
const year = parseInt(yearStr);
|
|
449
|
+
const month = parseInt(monthStr);
|
|
450
|
+
const day = parseInt(dayStr);
|
|
451
|
+
if (!this.isValidCalendarDate(year, month, day)) {
|
|
452
|
+
return "Invalid date - this date does not exist";
|
|
453
|
+
}
|
|
454
|
+
const dateObj = new Date(date);
|
|
455
|
+
const now = /* @__PURE__ */ new Date();
|
|
456
|
+
const minDate = /* @__PURE__ */ new Date("1900-01-01");
|
|
457
|
+
if (dateObj > now) {
|
|
458
|
+
return "Date cannot be in the future";
|
|
459
|
+
}
|
|
460
|
+
if (dateObj < minDate) {
|
|
461
|
+
return "Date cannot be before 1900";
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Helper function to validate calendar dates
|
|
467
|
+
* @param year - The year
|
|
468
|
+
* @param month - The month (1-12)
|
|
469
|
+
* @param day - The day (1-31)
|
|
470
|
+
*/
|
|
471
|
+
isValidCalendarDate(year, month, day) {
|
|
472
|
+
const date = new Date(year, month - 1, day);
|
|
473
|
+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Validates minimum length
|
|
477
|
+
* @param value - The value to validate
|
|
478
|
+
* @param minLength - Minimum required length
|
|
479
|
+
* @param fieldName - Optional field name for error message
|
|
480
|
+
*/
|
|
481
|
+
min_length(value, minLength, fieldName) {
|
|
482
|
+
if (!value || typeof value !== "string") {
|
|
483
|
+
return fieldName ? `${fieldName} is required.` : "This field is required.";
|
|
484
|
+
}
|
|
485
|
+
if (value.length < minLength) {
|
|
486
|
+
return fieldName ? `${fieldName} must be at least ${minLength} characters long.` : `Must be at least ${minLength} characters long.`;
|
|
487
|
+
}
|
|
488
|
+
return null;
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Validates maximum length
|
|
492
|
+
* @param value - The value to validate
|
|
493
|
+
* @param maxLength - Maximum allowed length
|
|
494
|
+
* @param fieldName - Optional field name for error message
|
|
495
|
+
*/
|
|
496
|
+
max_length(value, maxLength, fieldName) {
|
|
497
|
+
if (value && typeof value === "string" && value.length > maxLength) {
|
|
498
|
+
return fieldName ? `${fieldName} must be no more than ${maxLength} characters long.` : `Must be no more than ${maxLength} characters long.`;
|
|
499
|
+
}
|
|
500
|
+
return null;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Validates against a custom pattern
|
|
504
|
+
* @param value - The value to validate
|
|
505
|
+
* @param pattern - RegExp pattern to match
|
|
506
|
+
* @param errorMessage - Custom error message
|
|
507
|
+
*/
|
|
508
|
+
pattern(value, pattern, errorMessage) {
|
|
509
|
+
if (!value || typeof value !== "string") {
|
|
510
|
+
return errorMessage || "Please enter a valid value.";
|
|
511
|
+
}
|
|
512
|
+
if (!pattern.test(value)) {
|
|
513
|
+
return errorMessage || "Please enter a valid value.";
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Validates that a value contains only numbers
|
|
519
|
+
* @param value - The value to validate
|
|
520
|
+
* @param fieldName - Optional field name for error message
|
|
521
|
+
*/
|
|
522
|
+
numeric(value, fieldName) {
|
|
523
|
+
if (!value || typeof value !== "string") {
|
|
524
|
+
return fieldName ? `${fieldName} is required.` : "This field is required.";
|
|
525
|
+
}
|
|
526
|
+
if (!/^\d+$/.test(value.trim())) {
|
|
527
|
+
return fieldName ? `${fieldName} must contain only numbers.` : "Must contain only numbers.";
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Validates that a value contains only letters
|
|
533
|
+
* @param value - The value to validate
|
|
534
|
+
* @param fieldName - Optional field name for error message
|
|
535
|
+
*/
|
|
536
|
+
alphabetic(value, fieldName) {
|
|
537
|
+
if (!value || typeof value !== "string") {
|
|
538
|
+
return fieldName ? `${fieldName} is required.` : "This field is required.";
|
|
539
|
+
}
|
|
540
|
+
if (!/^[a-zA-Z\s]+$/.test(value.trim())) {
|
|
541
|
+
return fieldName ? `${fieldName} must contain only letters and spaces.` : "Must contain only letters and spaces.";
|
|
542
|
+
}
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Validates that a value contains only letters and numbers
|
|
547
|
+
* @param value - The value to validate
|
|
548
|
+
* @param fieldName - Optional field name for error message
|
|
549
|
+
*/
|
|
550
|
+
alphanumeric(value, fieldName) {
|
|
551
|
+
if (!value || typeof value !== "string") {
|
|
552
|
+
return fieldName ? `${fieldName} is required.` : "This field is required.";
|
|
553
|
+
}
|
|
554
|
+
if (!/^[a-zA-Z0-9\s]+$/.test(value.trim())) {
|
|
555
|
+
return fieldName ? `${fieldName} must contain only letters, numbers, and spaces.` : "Must contain only letters, numbers, and spaces.";
|
|
556
|
+
}
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Validates URL format
|
|
561
|
+
* @param url - The URL to validate
|
|
562
|
+
*/
|
|
563
|
+
url(url) {
|
|
564
|
+
if (!url || typeof url !== "string") {
|
|
565
|
+
return "Please enter a valid URL.";
|
|
566
|
+
}
|
|
567
|
+
const trimmed = url.trim();
|
|
568
|
+
try {
|
|
569
|
+
const urlObj = new URL(trimmed);
|
|
570
|
+
if (urlObj.hostname === "" || urlObj.hostname.startsWith(".") || urlObj.hostname.endsWith(".")) {
|
|
571
|
+
return "Please enter a valid URL.";
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
} catch {
|
|
575
|
+
return "Please enter a valid URL.";
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Validates postal code based on country
|
|
580
|
+
* @param code - The postal code to validate
|
|
581
|
+
* @param country - Country code (default: US)
|
|
582
|
+
*/
|
|
583
|
+
postal_code(code, country = "US") {
|
|
584
|
+
if (!code || typeof code !== "string") {
|
|
585
|
+
return "Please enter a valid postal code.";
|
|
586
|
+
}
|
|
587
|
+
const cleanCode = code.trim();
|
|
588
|
+
switch (country.toUpperCase()) {
|
|
589
|
+
case "US":
|
|
590
|
+
if (!/^\d{5}(-\d{4})?$/.test(cleanCode)) {
|
|
591
|
+
return "Please enter a valid US ZIP code (e.g., 12345 or 12345-6789).";
|
|
592
|
+
}
|
|
593
|
+
break;
|
|
594
|
+
case "CA":
|
|
595
|
+
if (!/^[A-Za-z]\d[A-Za-z] \d[A-Za-z]\d$/.test(cleanCode)) {
|
|
596
|
+
return "Please enter a valid Canadian postal code (e.g., A1A 1A1).";
|
|
597
|
+
}
|
|
598
|
+
break;
|
|
599
|
+
case "UK":
|
|
600
|
+
case "GB":
|
|
601
|
+
if (!/^[A-Za-z]{1,2}\d[A-Za-z\d]?\s\d[A-Za-z]{2}$/.test(cleanCode)) {
|
|
602
|
+
return "Please enter a valid UK postal code (e.g., SW1A 1AA).";
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
default:
|
|
606
|
+
if (!/^[A-Za-z0-9\s-]{3,10}$/.test(cleanCode)) {
|
|
607
|
+
return "Please enter a valid postal code.";
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
class AlcoreApiError extends Error {
|
|
614
|
+
constructor(errorCode, errorDescription, statusCode) {
|
|
615
|
+
super(`Alcore API Error ${errorCode}: ${errorDescription}`);
|
|
616
|
+
this.name = "AlcoreApiError";
|
|
617
|
+
this.errorCode = errorCode;
|
|
618
|
+
this.errorDescription = errorDescription;
|
|
619
|
+
this.statusCode = statusCode;
|
|
620
|
+
if (Error.captureStackTrace) {
|
|
621
|
+
Error.captureStackTrace(this, AlcoreApiError);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Check if error matches a specific error code
|
|
626
|
+
*/
|
|
627
|
+
isErrorCode(code) {
|
|
628
|
+
return this.errorCode === code;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Check if error matches any of the provided error codes
|
|
632
|
+
*/
|
|
633
|
+
isAnyErrorCode(codes) {
|
|
634
|
+
return codes.includes(this.errorCode);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const AlcoreErrorCodes = {
|
|
638
|
+
ALVIERE_CORE_NOT_INITIALIZED: "000000",
|
|
639
|
+
// Authentication errors (100xxx)
|
|
640
|
+
UNPROCESSABLE_ENCRYPTED_DATA: "100011",
|
|
641
|
+
WRONG_ENCRYPTED_ENDPOINT: "100012",
|
|
642
|
+
UNDECRYPTABLE_DATA_AT_ENDPOINT: "100013",
|
|
643
|
+
DECRYPTED_DATA_UNUSABLE_AT_ENDPOINT: "100014",
|
|
644
|
+
WRONG_DECRYPTED_ENDPOINT: "100015",
|
|
645
|
+
WRONG_VERSION_OR_ENDPOINT: "100016",
|
|
646
|
+
OPERATION_HALTED: "100017",
|
|
647
|
+
INVALID_REQUEST_PAYLOAD: "100300",
|
|
648
|
+
INVALID_REQUEST_PAYLOAD_FIELD: "100301",
|
|
649
|
+
INVALID_PATH_PARAMETER: "100302",
|
|
650
|
+
INVALID_HEADER: "100303",
|
|
651
|
+
INVALID_QUERY_PARAMETER: "100304",
|
|
652
|
+
INVALID_JWT: "100305",
|
|
653
|
+
AUTH_FAILED: "100306",
|
|
654
|
+
SESSION_NOT_FOUND: "110000",
|
|
655
|
+
PARENT_ACCOUNT_NOT_FOUND: "110001",
|
|
656
|
+
NOT_FOUND: "110002",
|
|
657
|
+
ACCOUNT_TYPE_NOT_SUPPORTED: "110004",
|
|
658
|
+
UNDERLYING_API_ERROR: "115000",
|
|
659
|
+
ACCOUNT_STATUS_NOT_ACTIVE: "115001",
|
|
660
|
+
ACCOUNT_CHILD_FORBIDDEN: "320129",
|
|
661
|
+
PARENT_ACCOUNT_NOT_ALLOWED: "320104"
|
|
662
|
+
// Add more error codes as you discover them from the API
|
|
663
|
+
};
|
|
664
|
+
const AlcoreErrorMessages = {
|
|
665
|
+
[AlcoreErrorCodes.INVALID_JWT]: "Invalid authentication token. Please log in again.",
|
|
666
|
+
[AlcoreErrorCodes.AUTH_FAILED]: "Authentication failed. Please check your credentials and try again.",
|
|
667
|
+
[AlcoreErrorCodes.SESSION_NOT_FOUND]: "Session not found. Please log in again.",
|
|
668
|
+
[AlcoreErrorCodes.PARENT_ACCOUNT_NOT_FOUND]: "Parent account not found.",
|
|
669
|
+
[AlcoreErrorCodes.NOT_FOUND]: "Resource not found.",
|
|
670
|
+
[AlcoreErrorCodes.ACCOUNT_TYPE_NOT_SUPPORTED]: "Account type not supported.",
|
|
671
|
+
[AlcoreErrorCodes.INVALID_REQUEST_PAYLOAD]: "Invalid request payload. Please check your request and try again.",
|
|
672
|
+
[AlcoreErrorCodes.INVALID_REQUEST_PAYLOAD_FIELD]: "Invalid request payload field. Please check your request and try again.",
|
|
673
|
+
[AlcoreErrorCodes.INVALID_PATH_PARAMETER]: "Invalid path parameter. Please check your request and try again.",
|
|
674
|
+
[AlcoreErrorCodes.INVALID_HEADER]: "Invalid header. Please check your request and try again.",
|
|
675
|
+
[AlcoreErrorCodes.INVALID_QUERY_PARAMETER]: "Invalid query parameter. Please check your request and try again.",
|
|
676
|
+
[AlcoreErrorCodes.UNPROCESSABLE_ENCRYPTED_DATA]: "Unprocessable encrypted data. Please check your request and try again.",
|
|
677
|
+
[AlcoreErrorCodes.WRONG_ENCRYPTED_ENDPOINT]: "Wrong encrypted endpoint. Please check your request and try again.",
|
|
678
|
+
[AlcoreErrorCodes.UNDECRYPTABLE_DATA_AT_ENDPOINT]: "Undecryptable data at endpoint. Please check your request and try again.",
|
|
679
|
+
[AlcoreErrorCodes.DECRYPTED_DATA_UNUSABLE_AT_ENDPOINT]: "Decrypted data unusable at endpoint. Please check your request and try again.",
|
|
680
|
+
[AlcoreErrorCodes.WRONG_DECRYPTED_ENDPOINT]: "Wrong decrypted endpoint. Please check your request and try again.",
|
|
681
|
+
[AlcoreErrorCodes.WRONG_VERSION_OR_ENDPOINT]: "Wrong version or endpoint. Please check your request and try again.",
|
|
682
|
+
[AlcoreErrorCodes.OPERATION_HALTED]: "Operation halted. Please check your request and try again.",
|
|
683
|
+
[AlcoreErrorCodes.UNDERLYING_API_ERROR]: "Underlying API Error. ",
|
|
684
|
+
[AlcoreErrorCodes.ACCOUNT_STATUS_NOT_ACTIVE]: "Account status not active. Please check your request and try again.",
|
|
685
|
+
[AlcoreErrorCodes.ACCOUNT_CHILD_FORBIDDEN]: "Used Account can't be used to create a child account.",
|
|
686
|
+
[AlcoreErrorCodes.PARENT_ACCOUNT_NOT_ALLOWED]: "Parent account not allowed. Please check your request and try again."
|
|
687
|
+
};
|
|
688
|
+
function getErrorMessage(errorCode) {
|
|
689
|
+
return AlcoreErrorMessages[errorCode] || "An unexpected error occurred. Please try again.";
|
|
690
|
+
}
|
|
691
|
+
const CriticalErrorCodes = [
|
|
692
|
+
AlcoreErrorCodes.INVALID_JWT,
|
|
693
|
+
AlcoreErrorCodes.AUTH_FAILED,
|
|
694
|
+
AlcoreErrorCodes.SESSION_NOT_FOUND,
|
|
695
|
+
AlcoreErrorCodes.UNPROCESSABLE_ENCRYPTED_DATA,
|
|
696
|
+
AlcoreErrorCodes.WRONG_ENCRYPTED_ENDPOINT,
|
|
697
|
+
AlcoreErrorCodes.UNDECRYPTABLE_DATA_AT_ENDPOINT,
|
|
698
|
+
AlcoreErrorCodes.OPERATION_HALTED,
|
|
699
|
+
AlcoreErrorCodes.ALVIERE_CORE_NOT_INITIALIZED,
|
|
700
|
+
AlcoreErrorCodes.UNDERLYING_API_ERROR,
|
|
701
|
+
AlcoreErrorCodes.ACCOUNT_STATUS_NOT_ACTIVE,
|
|
702
|
+
AlcoreErrorCodes.ACCOUNT_CHILD_FORBIDDEN,
|
|
703
|
+
AlcoreErrorCodes.PARENT_ACCOUNT_NOT_ALLOWED
|
|
704
|
+
];
|
|
705
|
+
function isCriticalError(errorCode) {
|
|
706
|
+
return CriticalErrorCodes.includes(errorCode);
|
|
707
|
+
}
|
|
708
|
+
class AlcoreBase {
|
|
709
|
+
/**
|
|
710
|
+
*
|
|
711
|
+
* @param jwt
|
|
712
|
+
* @param cryptor
|
|
713
|
+
* @param logger
|
|
714
|
+
*/
|
|
715
|
+
constructor(jwt, cryptor, logger) {
|
|
716
|
+
this.endpoint = "/alcore";
|
|
717
|
+
this.jwt = jwt;
|
|
718
|
+
this.cryptor = cryptor;
|
|
719
|
+
this.domain = getALVIERE_DOMAIN();
|
|
720
|
+
this.logger = logger;
|
|
721
|
+
this.logger.debug(`🔐 AlcoreBase initialized with domain: ${this.domain}`);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* send
|
|
725
|
+
*/
|
|
726
|
+
async send(method, endpoint, payload) {
|
|
727
|
+
const request = {
|
|
728
|
+
method,
|
|
729
|
+
endpoint,
|
|
730
|
+
payload: JSON.stringify(payload)
|
|
731
|
+
};
|
|
732
|
+
this.logger.debug("🌐 AlcoreBase: Preparing to send request");
|
|
733
|
+
this.logger.debug("🌐 Method: " + method);
|
|
734
|
+
this.logger.debug("🌐 Endpoint: " + endpoint);
|
|
735
|
+
this.logger.debug("🌐 Domain: " + (this.domain || "undefined"));
|
|
736
|
+
this.logger.debug("🌐 JWT Token: " + (this.jwt ? "✅ Present" : "❌ Missing"));
|
|
737
|
+
this.logger.debug("🌐 Payload size: " + JSON.stringify(payload).length + " chars");
|
|
738
|
+
this.logger.debug(this.jwt);
|
|
739
|
+
this.logger.debug("DATA TO SEND before encrypt");
|
|
740
|
+
this.logger.debug(payload);
|
|
741
|
+
let encryptedData;
|
|
742
|
+
try {
|
|
743
|
+
encryptedData = await this.cryptor.encrypt(request);
|
|
744
|
+
this.logger.debug("🔐 Encryption successful");
|
|
745
|
+
} catch (encryptError) {
|
|
746
|
+
this.logger.debug(
|
|
747
|
+
"❌ Encryption failed: " + (encryptError instanceof Error ? encryptError.message : "Unknown error")
|
|
748
|
+
);
|
|
749
|
+
throw encryptError;
|
|
750
|
+
}
|
|
751
|
+
this.logger.debug("BASE GATEWAY: posting to " + this.endpoint);
|
|
752
|
+
this.logger.debug(encryptedData);
|
|
753
|
+
const url = this.domain + this.endpoint;
|
|
754
|
+
this.logger.debug("🌐 Full URL: " + url);
|
|
755
|
+
try {
|
|
756
|
+
const response = await fetch(url, {
|
|
757
|
+
method: "post",
|
|
758
|
+
headers: {
|
|
759
|
+
"Content-Type": "application/json",
|
|
760
|
+
Authorization: "Bearer " + this.jwt,
|
|
761
|
+
Version: "2021-11-18"
|
|
762
|
+
},
|
|
763
|
+
body: JSON.stringify(encryptedData)
|
|
764
|
+
});
|
|
765
|
+
this.logger.debug(
|
|
766
|
+
"🌐 Response received - Status: " + response.status + " " + response.statusText
|
|
767
|
+
);
|
|
768
|
+
this.logger.debug("🌐 Response OK: " + response.ok);
|
|
769
|
+
if (!response.ok) {
|
|
770
|
+
let error = await response.json();
|
|
771
|
+
if (error.p) {
|
|
772
|
+
error = await this.decrypt(error);
|
|
773
|
+
}
|
|
774
|
+
this.logger.debug(
|
|
775
|
+
"❌ API Error - Code: " + error.error_code + ", Description: " + error.error_description
|
|
776
|
+
);
|
|
777
|
+
throw new AlcoreApiError(
|
|
778
|
+
response.status === 500 ? AlcoreErrorCodes.UNDERLYING_API_ERROR : error.error_code,
|
|
779
|
+
AlcoreErrorMessages[error.error_code],
|
|
780
|
+
response.status
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
return response;
|
|
784
|
+
} catch (fetchError) {
|
|
785
|
+
this.logger.debug(
|
|
786
|
+
"❌ Fetch failed: " + (fetchError instanceof Error ? fetchError.message : "Unknown error")
|
|
787
|
+
);
|
|
788
|
+
throw fetchError;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
*
|
|
793
|
+
* @param data
|
|
794
|
+
*/
|
|
795
|
+
async decrypt(data) {
|
|
796
|
+
return this.cryptor.decrypt(data);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function getSanitizedBody(data) {
|
|
800
|
+
const body = {};
|
|
801
|
+
for (const key in data) {
|
|
802
|
+
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
803
|
+
const value = data[key];
|
|
804
|
+
if (!["", null, void 0].includes(value)) {
|
|
805
|
+
body[key] = value;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return body;
|
|
810
|
+
}
|
|
811
|
+
function throwIfApiError(decrypted) {
|
|
812
|
+
if (decrypted && typeof decrypted === "object" && ("error_code" in decrypted || "error_description" in decrypted)) {
|
|
813
|
+
const errorMessage = decrypted.error_description || "Unknown API error";
|
|
814
|
+
const errorCode = decrypted.error_code || "UNKNOWN";
|
|
815
|
+
throw new Error(`${errorCode}: ${errorMessage}`);
|
|
816
|
+
}
|
|
817
|
+
return decrypted;
|
|
818
|
+
}
|
|
819
|
+
function decodeJWTPayload(jwt) {
|
|
820
|
+
try {
|
|
821
|
+
const parts = jwt.split(".");
|
|
822
|
+
if (parts.length !== 3) {
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
const payload = parts[1];
|
|
826
|
+
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
827
|
+
return JSON.parse(decoded);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
console.warn("Failed to decode JWT payload:", error);
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
function extractAlviereConfigFromJWT(jwt) {
|
|
834
|
+
const payload = decodeJWTPayload(jwt);
|
|
835
|
+
if (!payload) {
|
|
836
|
+
return {};
|
|
837
|
+
}
|
|
838
|
+
return {
|
|
839
|
+
domain: payload.api_domain || "https://api.dev.alviere.com"
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
function extractAccountUuidFromJWT(jwt) {
|
|
843
|
+
const payload = decodeJWTPayload(jwt);
|
|
844
|
+
if (!payload) {
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
return payload.sub || null;
|
|
848
|
+
}
|
|
849
|
+
function isJWTExpired(jwt) {
|
|
850
|
+
const payload = decodeJWTPayload(jwt);
|
|
851
|
+
if (!payload || !payload.exp) {
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
855
|
+
return payload.exp < now;
|
|
856
|
+
}
|
|
857
|
+
function getJWTExpiration(jwt) {
|
|
858
|
+
const payload = decodeJWTPayload(jwt);
|
|
859
|
+
if (!payload || !payload.exp) {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
return new Date(payload.exp * 1e3);
|
|
863
|
+
}
|
|
864
|
+
class PaymentsGateway extends AlcoreBase {
|
|
865
|
+
/**
|
|
866
|
+
*
|
|
867
|
+
* @param jwt
|
|
868
|
+
* @param cryptor
|
|
869
|
+
*/
|
|
870
|
+
constructor(jwt, cryptor, logger) {
|
|
871
|
+
super(jwt, cryptor, logger);
|
|
872
|
+
}
|
|
873
|
+
async add_card(accountUuid, data) {
|
|
874
|
+
const endpoint = `/accounts/${accountUuid}/payment-methods/cards`;
|
|
875
|
+
const response = await this.send("POST", endpoint, data);
|
|
876
|
+
const encrypted = await response.json();
|
|
877
|
+
const decrypted = await this.decrypt(encrypted);
|
|
878
|
+
return throwIfApiError(decrypted);
|
|
879
|
+
}
|
|
880
|
+
async add_bank_account(accountUuid, data) {
|
|
881
|
+
const endpoint = `/accounts/${accountUuid}/payment-methods/bank-accounts`;
|
|
882
|
+
const response = await this.send("POST", endpoint, data);
|
|
883
|
+
const encrypted = await response.json();
|
|
884
|
+
const decrypted = await this.decrypt(encrypted);
|
|
885
|
+
return throwIfApiError(decrypted);
|
|
886
|
+
}
|
|
887
|
+
async get_bank_accounts(accountUuid) {
|
|
888
|
+
const endpoint = `/accounts/${accountUuid}/payment-methods/bank-accounts`;
|
|
889
|
+
const response = await this.send("GET", endpoint, {});
|
|
890
|
+
const encrypted = await response.json();
|
|
891
|
+
const decrypted = await this.decrypt(encrypted);
|
|
892
|
+
return throwIfApiError(decrypted);
|
|
893
|
+
}
|
|
894
|
+
async delete_bank_account(accountUuid, uuid) {
|
|
895
|
+
const endpoint = `/accounts/${accountUuid}/payment-methods/bank-accounts/${uuid}`;
|
|
896
|
+
const response = await this.send("DELETE", endpoint, {});
|
|
897
|
+
return throwIfApiError(response);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
class WalletsGateway extends AlcoreBase {
|
|
901
|
+
constructor(jwt, cryptor, logger) {
|
|
902
|
+
super(jwt, cryptor, logger);
|
|
903
|
+
}
|
|
904
|
+
async listWallets(accountUuid, params) {
|
|
905
|
+
const query = params ? "?" + new URLSearchParams(
|
|
906
|
+
Object.entries(params).filter(([_, v]) => v !== void 0)
|
|
907
|
+
) : "";
|
|
908
|
+
const endpoint = `/accounts/${accountUuid}/wallets${query}`;
|
|
909
|
+
const response = await this.send("GET", endpoint, {});
|
|
910
|
+
const encrypted = await response.json();
|
|
911
|
+
const decrypted = await this.decrypt(encrypted);
|
|
912
|
+
return throwIfApiError(decrypted);
|
|
913
|
+
}
|
|
914
|
+
// async getWallet(accountUuid: string, walletUuid: string): Promise<WalletResponse> {
|
|
915
|
+
// throw new Error('getWallet not implemented');
|
|
916
|
+
// }
|
|
917
|
+
async loadFunds(walletUuid, data) {
|
|
918
|
+
const endpoint = `/wallets/${walletUuid}/load`;
|
|
919
|
+
const response = await this.send("POST", endpoint, data);
|
|
920
|
+
const encrypted = await response.json();
|
|
921
|
+
const decrypted = await this.decrypt(encrypted);
|
|
922
|
+
return throwIfApiError(decrypted);
|
|
923
|
+
}
|
|
924
|
+
// async withdrawFunds(walletUuid: string, data: WithdrawFundsRequest): Promise<WalletTransactionResponse> {
|
|
925
|
+
// throw new Error('withdrawFunds not implemented');
|
|
926
|
+
// }
|
|
927
|
+
// async sendFunds(walletUuid: string, data: SendFundsRequest): Promise<WalletTransactionResponse> {
|
|
928
|
+
// throw new Error('sendFunds not implemented');
|
|
929
|
+
// }
|
|
930
|
+
// async transferFunds(walletUuid: string, data: TransferFundsRequest): Promise<WalletTransactionResponse> {
|
|
931
|
+
// throw new Error('transferFunds not implemented');
|
|
932
|
+
// }
|
|
933
|
+
// async creditFunds(walletUuid: string, data: CreditFundsRequest): Promise<WalletTransactionResponse> {
|
|
934
|
+
// throw new Error('creditFunds not implemented');
|
|
935
|
+
// }
|
|
936
|
+
// async creditPromoFunds(walletUuid: string, data: PromoFundsRequest): Promise<WalletTransactionResponse> {
|
|
937
|
+
// throw new Error('creditPromoFunds not implemented');
|
|
938
|
+
// }
|
|
939
|
+
// async getWalletStatement(walletUuid: string, monthYear: string, scope?: 'DEBITS' | 'CREDITS' | 'FEES' | 'ALL'): Promise<WalletStatementResponse> {
|
|
940
|
+
// throw new Error('getWalletStatement not implemented');
|
|
941
|
+
// }
|
|
942
|
+
}
|
|
943
|
+
class PaymentInstrumentsGateway extends AlcoreBase {
|
|
944
|
+
constructor(jwt, cryptor, logger) {
|
|
945
|
+
super(jwt, cryptor, logger);
|
|
946
|
+
}
|
|
947
|
+
async createPaymentInstrument(data) {
|
|
948
|
+
const endpoint = "/payments/payment-instruments";
|
|
949
|
+
const response = await this.send("POST", endpoint, data);
|
|
950
|
+
const encrypted = await response.json();
|
|
951
|
+
const decrypted = await this.decrypt(encrypted);
|
|
952
|
+
return throwIfApiError(decrypted);
|
|
953
|
+
}
|
|
954
|
+
async getPaymentInstrument(paymentInstrumentUuid) {
|
|
955
|
+
const endpoint = `/payments/payment-instruments/${paymentInstrumentUuid}`;
|
|
956
|
+
const response = await this.send("GET", endpoint, {});
|
|
957
|
+
const encrypted = await response.json();
|
|
958
|
+
const decrypted = await this.decrypt(encrypted);
|
|
959
|
+
return throwIfApiError(decrypted);
|
|
960
|
+
}
|
|
961
|
+
async deletePaymentInstrument(paymentInstrumentUuid) {
|
|
962
|
+
const endpoint = `/payments/payment-instruments/${paymentInstrumentUuid}`;
|
|
963
|
+
await this.send("DELETE", endpoint, {});
|
|
964
|
+
}
|
|
965
|
+
async debitFunds(data) {
|
|
966
|
+
const endpoint = "/payments/debit";
|
|
967
|
+
const response = await this.send("POST", endpoint, data);
|
|
968
|
+
const encrypted = await response.json();
|
|
969
|
+
const decrypted = await this.decrypt(encrypted);
|
|
970
|
+
return throwIfApiError(decrypted);
|
|
971
|
+
}
|
|
972
|
+
async listCards(accountUuid, params) {
|
|
973
|
+
const query = params ? "?" + new URLSearchParams(Object.entries(params).filter(([_, v]) => v !== void 0)) : "";
|
|
974
|
+
const endpoint = `/accounts/${accountUuid}/payment-methods/cards${query}`;
|
|
975
|
+
const response = await this.send("GET", endpoint, {});
|
|
976
|
+
const encrypted = await response.json();
|
|
977
|
+
const decrypted = await this.decrypt(encrypted);
|
|
978
|
+
return throwIfApiError(decrypted);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
class AccountsGateway extends AlcoreBase {
|
|
982
|
+
constructor(jwt, cryptor, logger) {
|
|
983
|
+
super(jwt, cryptor, logger);
|
|
984
|
+
this.logger.debug("🔐 AccountsGateway initialized");
|
|
985
|
+
}
|
|
986
|
+
async createAccount(data) {
|
|
987
|
+
const endpoint = "/accounts";
|
|
988
|
+
const response = await this.send("POST", endpoint, data);
|
|
989
|
+
const encrypted = await response.json();
|
|
990
|
+
const decrypted = await this.decrypt(encrypted);
|
|
991
|
+
return throwIfApiError(decrypted);
|
|
992
|
+
}
|
|
993
|
+
async getAccount(accountUuid) {
|
|
994
|
+
const endpoint = `/accounts/${accountUuid}`;
|
|
995
|
+
const response = await this.send("GET", endpoint, {});
|
|
996
|
+
const encrypted = await response.json();
|
|
997
|
+
const decrypted = await this.decrypt(encrypted);
|
|
998
|
+
return throwIfApiError(decrypted);
|
|
999
|
+
}
|
|
1000
|
+
async updateAccount(accountUuid, data) {
|
|
1001
|
+
const endpoint = `/accounts/${accountUuid}`;
|
|
1002
|
+
const response = await this.send("PUT", endpoint, data);
|
|
1003
|
+
const encrypted = await response.json();
|
|
1004
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1005
|
+
return throwIfApiError(decrypted);
|
|
1006
|
+
}
|
|
1007
|
+
async deleteAccount(accountUuid) {
|
|
1008
|
+
const endpoint = `/accounts/${accountUuid}`;
|
|
1009
|
+
await this.send("DELETE", endpoint, {});
|
|
1010
|
+
}
|
|
1011
|
+
async listAccounts(params) {
|
|
1012
|
+
const query = params ? "?" + new URLSearchParams(Object.entries(params).filter(([_, v]) => v !== void 0)) : "";
|
|
1013
|
+
const endpoint = `/accounts${query}`;
|
|
1014
|
+
const response = await this.send("GET", endpoint, {});
|
|
1015
|
+
const encrypted = await response.json();
|
|
1016
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1017
|
+
return throwIfApiError(decrypted);
|
|
1018
|
+
}
|
|
1019
|
+
async activateAccount(accountUuid) {
|
|
1020
|
+
const endpoint = `/accounts/${accountUuid}/activate`;
|
|
1021
|
+
await this.send("POST", endpoint, {});
|
|
1022
|
+
}
|
|
1023
|
+
async deactivateAccount(accountUuid) {
|
|
1024
|
+
const endpoint = `/accounts/${accountUuid}/deactivate`;
|
|
1025
|
+
await this.send("POST", endpoint, {});
|
|
1026
|
+
}
|
|
1027
|
+
async manageOnboarding(accountUuid, action) {
|
|
1028
|
+
const endpoint = `/accounts/${accountUuid}/onboarding`;
|
|
1029
|
+
await this.send("POST", endpoint, { action });
|
|
1030
|
+
}
|
|
1031
|
+
async createAddress(accountUuid, data) {
|
|
1032
|
+
const endpoint = `/accounts/${accountUuid}/addresses`;
|
|
1033
|
+
const response = await this.send("POST", endpoint, data);
|
|
1034
|
+
const encrypted = await response.json();
|
|
1035
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1036
|
+
return throwIfApiError(decrypted);
|
|
1037
|
+
}
|
|
1038
|
+
async getAddresses(accountUuid, params) {
|
|
1039
|
+
const query = params ? "?" + new URLSearchParams(Object.entries(params).filter(([_, v]) => v !== void 0)) : "";
|
|
1040
|
+
const endpoint = `/accounts/${accountUuid}/addresses${query}`;
|
|
1041
|
+
const response = await this.send("GET", endpoint, {});
|
|
1042
|
+
const encrypted = await response.json();
|
|
1043
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1044
|
+
return throwIfApiError(decrypted);
|
|
1045
|
+
}
|
|
1046
|
+
async updateAddress(accountUuid, addressUuid, data) {
|
|
1047
|
+
const endpoint = `/accounts/${accountUuid}/addresses/${addressUuid}`;
|
|
1048
|
+
const response = await this.send("PUT", endpoint, data);
|
|
1049
|
+
const encrypted = await response.json();
|
|
1050
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1051
|
+
return throwIfApiError(decrypted);
|
|
1052
|
+
}
|
|
1053
|
+
async deleteAddress(accountUuid, addressUuid) {
|
|
1054
|
+
const endpoint = `/accounts/${accountUuid}/addresses/${addressUuid}`;
|
|
1055
|
+
await this.send("DELETE", endpoint, {});
|
|
1056
|
+
}
|
|
1057
|
+
async createDossier(accountUuid, data) {
|
|
1058
|
+
const endpoint = `/accounts/${accountUuid}/dossiers`;
|
|
1059
|
+
const response = await this.send("POST", endpoint, data);
|
|
1060
|
+
const encrypted = await response.json();
|
|
1061
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1062
|
+
return throwIfApiError(decrypted);
|
|
1063
|
+
}
|
|
1064
|
+
async getDossiers(accountUuid, params) {
|
|
1065
|
+
const query = params ? "?" + new URLSearchParams(Object.entries(params).filter(([_, v]) => v !== void 0)) : "";
|
|
1066
|
+
const endpoint = `/accounts/${accountUuid}/dossiers${query}`;
|
|
1067
|
+
const response = await this.send("GET", endpoint, {});
|
|
1068
|
+
const encrypted = await response.json();
|
|
1069
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1070
|
+
return throwIfApiError(decrypted);
|
|
1071
|
+
}
|
|
1072
|
+
async getDossier(accountUuid, dossierUuid) {
|
|
1073
|
+
const endpoint = `/accounts/${accountUuid}/dossiers/${dossierUuid}`;
|
|
1074
|
+
const response = await this.send("GET", endpoint, {});
|
|
1075
|
+
const encrypted = await response.json();
|
|
1076
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1077
|
+
return throwIfApiError(decrypted);
|
|
1078
|
+
}
|
|
1079
|
+
async updateDossier(accountUuid, dossierUuid, data) {
|
|
1080
|
+
const endpoint = `/accounts/${accountUuid}/dossiers/${dossierUuid}`;
|
|
1081
|
+
const response = await this.send("PUT", endpoint, data);
|
|
1082
|
+
const encrypted = await response.json();
|
|
1083
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1084
|
+
return throwIfApiError(decrypted);
|
|
1085
|
+
}
|
|
1086
|
+
async replaceDossier(accountUuid, dossierUuid, data) {
|
|
1087
|
+
const endpoint = `/accounts/${accountUuid}/dossiers/${dossierUuid}`;
|
|
1088
|
+
const response = await this.send("PATCH", endpoint, data);
|
|
1089
|
+
const encrypted = await response.json();
|
|
1090
|
+
const decrypted = await this.decrypt(encrypted);
|
|
1091
|
+
return throwIfApiError(decrypted);
|
|
1092
|
+
}
|
|
1093
|
+
async deleteDossier(accountUuid, dossierUuid) {
|
|
1094
|
+
const endpoint = `/accounts/${accountUuid}/dossiers/${dossierUuid}`;
|
|
1095
|
+
await this.send("DELETE", endpoint, {});
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
class AuthGateway extends AlcoreBase {
|
|
1099
|
+
constructor(jwt, cryptor, logger) {
|
|
1100
|
+
super(jwt, cryptor, logger);
|
|
1101
|
+
this.logger.debug(`🔐 AuthGateway initialized`);
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Generate scoped JWT for specific account (JWT downgrade)
|
|
1105
|
+
* Exchanges business-level JWT for consumer-level JWT
|
|
1106
|
+
* Uses AlcoreBase.send() to follow the same pattern as all other gateways:
|
|
1107
|
+
* - Endpoint passed in encrypted payload
|
|
1108
|
+
* - Request sent to /alcore (not /alcore/v3/auth)
|
|
1109
|
+
* - Middleware handles routing
|
|
1110
|
+
*
|
|
1111
|
+
* @param data - Request containing account_uuid to scope to
|
|
1112
|
+
* @returns New scoped JWT token
|
|
1113
|
+
*/
|
|
1114
|
+
async generateScopedToken(data) {
|
|
1115
|
+
this.logger.debug("🔐 AuthGateway: Generating scoped token");
|
|
1116
|
+
this.logger.debug("🌐 Endpoint: /v3/auth");
|
|
1117
|
+
this.logger.debug("🌐 Account UUID: " + data.account_uuid);
|
|
1118
|
+
const response = await this.send("POST", "/v3/auth", data);
|
|
1119
|
+
const decryptedResponse = await this.decrypt(await response.json());
|
|
1120
|
+
this.logger.debug("✅ Scoped token generated successfully");
|
|
1121
|
+
return decryptedResponse;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
class WalletsApiService {
|
|
1125
|
+
constructor(gateway) {
|
|
1126
|
+
this.gateway = gateway;
|
|
1127
|
+
}
|
|
1128
|
+
async listWallets(accountUuid, params) {
|
|
1129
|
+
return this.gateway.listWallets(accountUuid, params);
|
|
1130
|
+
}
|
|
1131
|
+
async loadFunds(walletUuid, params) {
|
|
1132
|
+
return this.gateway.loadFunds(walletUuid, params);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
class PaymentInstrumentsApiService {
|
|
1136
|
+
constructor(gateway) {
|
|
1137
|
+
this.gateway = gateway;
|
|
1138
|
+
}
|
|
1139
|
+
async createPaymentInstrument(data) {
|
|
1140
|
+
return this.gateway.createPaymentInstrument(data);
|
|
1141
|
+
}
|
|
1142
|
+
async getPaymentInstrument(paymentInstrumentUuid) {
|
|
1143
|
+
return this.gateway.getPaymentInstrument(paymentInstrumentUuid);
|
|
1144
|
+
}
|
|
1145
|
+
async deletePaymentInstrument(paymentInstrumentUuid) {
|
|
1146
|
+
return this.gateway.deletePaymentInstrument(paymentInstrumentUuid);
|
|
1147
|
+
}
|
|
1148
|
+
async debitFunds(data) {
|
|
1149
|
+
return this.gateway.debitFunds(data);
|
|
1150
|
+
}
|
|
1151
|
+
async listCards(accountUuid, params) {
|
|
1152
|
+
return this.gateway.listCards(accountUuid, params);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
class AccountsApiService {
|
|
1156
|
+
constructor(gateway) {
|
|
1157
|
+
this.gateway = gateway;
|
|
1158
|
+
}
|
|
1159
|
+
async createAccount(data) {
|
|
1160
|
+
return this.gateway.createAccount(data);
|
|
1161
|
+
}
|
|
1162
|
+
async getAccount(accountUuid) {
|
|
1163
|
+
return this.gateway.getAccount(accountUuid);
|
|
1164
|
+
}
|
|
1165
|
+
async updateAccount(accountUuid, data) {
|
|
1166
|
+
return this.gateway.updateAccount(accountUuid, data);
|
|
1167
|
+
}
|
|
1168
|
+
async deleteAccount(accountUuid) {
|
|
1169
|
+
return this.gateway.deleteAccount(accountUuid);
|
|
1170
|
+
}
|
|
1171
|
+
async listAccounts(params) {
|
|
1172
|
+
return this.gateway.listAccounts(params);
|
|
1173
|
+
}
|
|
1174
|
+
async activateAccount(accountUuid) {
|
|
1175
|
+
return this.gateway.activateAccount(accountUuid);
|
|
1176
|
+
}
|
|
1177
|
+
async deactivateAccount(accountUuid) {
|
|
1178
|
+
return this.gateway.deactivateAccount(accountUuid);
|
|
1179
|
+
}
|
|
1180
|
+
async manageOnboarding(accountUuid, action) {
|
|
1181
|
+
return this.gateway.manageOnboarding(accountUuid, action);
|
|
1182
|
+
}
|
|
1183
|
+
async createAddress(accountUuid, data) {
|
|
1184
|
+
return this.gateway.createAddress(accountUuid, data);
|
|
1185
|
+
}
|
|
1186
|
+
async getAddresses(accountUuid, params) {
|
|
1187
|
+
return this.gateway.getAddresses(accountUuid, params);
|
|
1188
|
+
}
|
|
1189
|
+
async updateAddress(accountUuid, addressUuid, data) {
|
|
1190
|
+
return this.gateway.updateAddress(accountUuid, addressUuid, data);
|
|
1191
|
+
}
|
|
1192
|
+
async deleteAddress(accountUuid, addressUuid) {
|
|
1193
|
+
return this.gateway.deleteAddress(accountUuid, addressUuid);
|
|
1194
|
+
}
|
|
1195
|
+
async createDossier(accountUuid, data) {
|
|
1196
|
+
return this.gateway.createDossier(accountUuid, data);
|
|
1197
|
+
}
|
|
1198
|
+
async getDossiers(accountUuid, params) {
|
|
1199
|
+
return this.gateway.getDossiers(accountUuid, params);
|
|
1200
|
+
}
|
|
1201
|
+
async getDossier(accountUuid, dossierUuid) {
|
|
1202
|
+
return this.gateway.getDossier(accountUuid, dossierUuid);
|
|
1203
|
+
}
|
|
1204
|
+
async updateDossier(accountUuid, dossierUuid, data) {
|
|
1205
|
+
return this.gateway.updateDossier(accountUuid, dossierUuid, data);
|
|
1206
|
+
}
|
|
1207
|
+
async replaceDossier(accountUuid, dossierUuid, data) {
|
|
1208
|
+
return this.gateway.replaceDossier(accountUuid, dossierUuid, data);
|
|
1209
|
+
}
|
|
1210
|
+
async deleteDossier(accountUuid, dossierUuid) {
|
|
1211
|
+
return this.gateway.deleteDossier(accountUuid, dossierUuid);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
class PaymentProcessor {
|
|
1215
|
+
constructor(gateway, logger) {
|
|
1216
|
+
this.gateway = gateway;
|
|
1217
|
+
this.logger = logger;
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Process an add card request through the gateway
|
|
1221
|
+
*/
|
|
1222
|
+
async processAddCard(accountUuid, request) {
|
|
1223
|
+
this.logger.debug("PaymentProcessor: Starting add card processing");
|
|
1224
|
+
this.logger.debug("PaymentProcessor: Request data: " + JSON.stringify(request, null, 2));
|
|
1225
|
+
try {
|
|
1226
|
+
const result = await this.gateway.add_card(accountUuid, request);
|
|
1227
|
+
this.logger.debug("PaymentProcessor: Add card processing completed successfully");
|
|
1228
|
+
return result;
|
|
1229
|
+
} catch (error) {
|
|
1230
|
+
this.logger.debug("PaymentProcessor: Add card processing failed");
|
|
1231
|
+
this.logger.debug("PaymentProcessor: Error details: " + JSON.stringify(error));
|
|
1232
|
+
throw error;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
/**
|
|
1236
|
+
* Validate that a request has all required fields before processing
|
|
1237
|
+
*/
|
|
1238
|
+
validateRequest(request) {
|
|
1239
|
+
const requiredFields = [
|
|
1240
|
+
"external_id",
|
|
1241
|
+
"pan",
|
|
1242
|
+
"exp_year",
|
|
1243
|
+
"exp_month",
|
|
1244
|
+
"postal_code",
|
|
1245
|
+
"security_code"
|
|
1246
|
+
];
|
|
1247
|
+
for (const field of requiredFields) {
|
|
1248
|
+
if (!request[field]) {
|
|
1249
|
+
throw new Error(`Missing required field: ${field}`);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
this.logger.debug("PaymentProcessor: Request validation passed");
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Process payment with pre-validation
|
|
1256
|
+
*/
|
|
1257
|
+
async processAddCardWithValidation(accountUuid, request) {
|
|
1258
|
+
this.logger.debug("PaymentProcessor: Starting add card processing with validation");
|
|
1259
|
+
this.validateRequest(request);
|
|
1260
|
+
return await this.processAddCard(accountUuid, request);
|
|
1261
|
+
}
|
|
1262
|
+
/**
|
|
1263
|
+
* Process an add bank account request through the gateway
|
|
1264
|
+
*/
|
|
1265
|
+
async processAddBankAccount(accountUuid, request) {
|
|
1266
|
+
this.logger.debug("PaymentProcessor: Starting add bank account processing");
|
|
1267
|
+
this.logger.debug("PaymentProcessor: Request data: " + JSON.stringify(request, null, 2));
|
|
1268
|
+
try {
|
|
1269
|
+
const result = await this.gateway.add_bank_account(accountUuid, request);
|
|
1270
|
+
this.logger.debug("PaymentProcessor: Add bank account processing completed successfully");
|
|
1271
|
+
return result;
|
|
1272
|
+
} catch (error) {
|
|
1273
|
+
this.logger.debug("PaymentProcessor: Add bank account processing failed");
|
|
1274
|
+
this.logger.debug("PaymentProcessor: Error details: " + JSON.stringify(error));
|
|
1275
|
+
throw error;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Validate that a bank account request has all required fields before processing
|
|
1280
|
+
*/
|
|
1281
|
+
validateBankAccountRequest(request) {
|
|
1282
|
+
const requiredFields = [
|
|
1283
|
+
"external_id",
|
|
1284
|
+
"country",
|
|
1285
|
+
"currency",
|
|
1286
|
+
"bank_account_details"
|
|
1287
|
+
];
|
|
1288
|
+
for (const field of requiredFields) {
|
|
1289
|
+
if (!request[field]) {
|
|
1290
|
+
throw new Error(`Missing required field: ${field}`);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
this.logger.debug("PaymentProcessor: Bank account request validation passed");
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Process bank account addition with pre-validation
|
|
1297
|
+
*/
|
|
1298
|
+
async processAddBankAccountWithValidation(accountUuid, request) {
|
|
1299
|
+
this.logger.debug("PaymentProcessor: Starting add bank account processing with validation");
|
|
1300
|
+
this.validateBankAccountRequest(request);
|
|
1301
|
+
return await this.processAddBankAccount(accountUuid, request);
|
|
1302
|
+
}
|
|
1303
|
+
async processGetBankAccounts(accountUuid) {
|
|
1304
|
+
this.logger.debug("PaymentProcessor: Starting get bank accounts processing");
|
|
1305
|
+
try {
|
|
1306
|
+
const result = await this.gateway.get_bank_accounts(accountUuid);
|
|
1307
|
+
this.logger.debug("PaymentProcessor: Get bank accounts processing completed successfully");
|
|
1308
|
+
return result;
|
|
1309
|
+
} catch (error) {
|
|
1310
|
+
this.logger.debug("PaymentProcessor: Get bank accounts processing failed");
|
|
1311
|
+
this.logger.debug("PaymentProcessor: Error details: " + JSON.stringify(error));
|
|
1312
|
+
throw error;
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
async processDeleteBankAccount(accountUuid, uuid) {
|
|
1316
|
+
this.logger.debug("PaymentProcessor: Starting delete bank account processing");
|
|
1317
|
+
try {
|
|
1318
|
+
const result = await this.gateway.delete_bank_account(accountUuid, uuid);
|
|
1319
|
+
this.logger.debug("PaymentProcessor: Delete bank account processing completed successfully");
|
|
1320
|
+
return result;
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
this.logger.debug("PaymentProcessor: Delete bank account processing failed");
|
|
1323
|
+
this.logger.debug("PaymentProcessor: Error details: " + JSON.stringify(error));
|
|
1324
|
+
throw error;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
function NewAddCardRequest(params) {
|
|
1329
|
+
const { external_id, name, pan, expiry, code, zip } = params;
|
|
1330
|
+
const expiryParts = expiry.split("/");
|
|
1331
|
+
const month = expiryParts[0];
|
|
1332
|
+
const year = expiryParts[1];
|
|
1333
|
+
let data = {
|
|
1334
|
+
external_id,
|
|
1335
|
+
pan,
|
|
1336
|
+
exp_year: year,
|
|
1337
|
+
exp_month: month,
|
|
1338
|
+
security_code: code,
|
|
1339
|
+
postal_code: zip,
|
|
1340
|
+
name_on_card: name
|
|
1341
|
+
};
|
|
1342
|
+
data = getSanitizedBody(data);
|
|
1343
|
+
return data;
|
|
1344
|
+
}
|
|
1345
|
+
function NewPaymentInstrumentRequest(accountUuid, paymentInstrumentType, cardDetails, externalId, metadata) {
|
|
1346
|
+
return {
|
|
1347
|
+
account_uuid: accountUuid,
|
|
1348
|
+
external_id: externalId,
|
|
1349
|
+
payment_instrument_type: paymentInstrumentType,
|
|
1350
|
+
payment_instrument_details: {
|
|
1351
|
+
card: cardDetails
|
|
1352
|
+
},
|
|
1353
|
+
metadata
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
function NewCardInstrumentFromForm(pan, expiry, securityCode, nameOnCard, addressLine1, city, state, postal_code, country, addressLine2, phoneNumber, emailAddress) {
|
|
1357
|
+
const expiryParts = expiry.split("/");
|
|
1358
|
+
const exp_month = expiryParts[0];
|
|
1359
|
+
const exp_year = expiryParts[1];
|
|
1360
|
+
return {
|
|
1361
|
+
pan: pan.replace(/\s/g, ""),
|
|
1362
|
+
// Remove spaces
|
|
1363
|
+
exp_month,
|
|
1364
|
+
exp_year,
|
|
1365
|
+
security_code: securityCode,
|
|
1366
|
+
name_on_card: nameOnCard,
|
|
1367
|
+
phone_number: phoneNumber,
|
|
1368
|
+
email_address: emailAddress,
|
|
1369
|
+
billing_address: {
|
|
1370
|
+
line_1: addressLine1,
|
|
1371
|
+
line_2: addressLine2,
|
|
1372
|
+
city,
|
|
1373
|
+
state,
|
|
1374
|
+
postal_code,
|
|
1375
|
+
country
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
function NewDebitFundsRequest(walletUuid, externalId, amount, currency, merchantDetails, authType = "AUTHCAP", paymentInstrumentUuid, paymentInstrument, description, threeDSOptions, metadata) {
|
|
1380
|
+
return {
|
|
1381
|
+
wallet_uuid: walletUuid,
|
|
1382
|
+
external_id: externalId,
|
|
1383
|
+
amount,
|
|
1384
|
+
currency,
|
|
1385
|
+
auth_type: authType,
|
|
1386
|
+
payment_instrument_uuid: paymentInstrumentUuid,
|
|
1387
|
+
payment_instrument: paymentInstrument,
|
|
1388
|
+
description,
|
|
1389
|
+
merchant_details: merchantDetails,
|
|
1390
|
+
"3ds_options": threeDSOptions,
|
|
1391
|
+
metadata
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
function createDefaultMerchantDetails() {
|
|
1395
|
+
return {
|
|
1396
|
+
name: "Demo Merchant Store",
|
|
1397
|
+
merchant_id: "DEMO_MERCHANT_001"
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
function NewAddBankAccountRequest(params) {
|
|
1401
|
+
const { external_id, country, currency, bank_account_details, metadata } = params;
|
|
1402
|
+
let data = {
|
|
1403
|
+
external_id,
|
|
1404
|
+
country,
|
|
1405
|
+
currency,
|
|
1406
|
+
bank_account_details,
|
|
1407
|
+
metadata
|
|
1408
|
+
};
|
|
1409
|
+
data = getSanitizedBody(data);
|
|
1410
|
+
return data;
|
|
1411
|
+
}
|
|
1412
|
+
function NewLoadFundsRequest(params) {
|
|
1413
|
+
const data = {
|
|
1414
|
+
payment_method_uuid: params.payment_method_uuid,
|
|
1415
|
+
amount: params.amount,
|
|
1416
|
+
external_id: params.external_id,
|
|
1417
|
+
service_fees: params.service_fees,
|
|
1418
|
+
transaction_options: params.transaction_options,
|
|
1419
|
+
metadata: params.metadata
|
|
1420
|
+
};
|
|
1421
|
+
return getSanitizedBody(data);
|
|
1422
|
+
}
|
|
1423
|
+
function NewWithdrawFundsRequest(params) {
|
|
1424
|
+
const data = {
|
|
1425
|
+
payment_method_uuid: params.payment_method_uuid,
|
|
1426
|
+
amount: params.amount,
|
|
1427
|
+
external_id: params.external_id,
|
|
1428
|
+
service_fees: params.service_fees,
|
|
1429
|
+
transaction_options: params.transaction_options,
|
|
1430
|
+
metadata: params.metadata
|
|
1431
|
+
};
|
|
1432
|
+
return getSanitizedBody(data);
|
|
1433
|
+
}
|
|
1434
|
+
function NewSendFundsRequest(params) {
|
|
1435
|
+
const data = {
|
|
1436
|
+
destination_wallet_uuid: params.destination_wallet_uuid,
|
|
1437
|
+
external_id: params.external_id,
|
|
1438
|
+
amount: params.amount,
|
|
1439
|
+
service_fees: params.service_fees,
|
|
1440
|
+
description: params.description,
|
|
1441
|
+
metadata: params.metadata
|
|
1442
|
+
};
|
|
1443
|
+
return getSanitizedBody(data);
|
|
1444
|
+
}
|
|
1445
|
+
function NewTransferFundsRequest(params) {
|
|
1446
|
+
const data = {
|
|
1447
|
+
beneficiary_uuid: params.beneficiary_uuid,
|
|
1448
|
+
payout_method_uuid: params.payout_method_uuid,
|
|
1449
|
+
amount: params.amount,
|
|
1450
|
+
service_fees: params.service_fees,
|
|
1451
|
+
description: params.description,
|
|
1452
|
+
external_id: params.external_id,
|
|
1453
|
+
metadata: params.metadata
|
|
1454
|
+
};
|
|
1455
|
+
return getSanitizedBody(data);
|
|
1456
|
+
}
|
|
1457
|
+
function NewCreditFundsRequest(params) {
|
|
1458
|
+
const data = {
|
|
1459
|
+
amount: params.amount,
|
|
1460
|
+
currency: params.currency,
|
|
1461
|
+
external_id: params.external_id,
|
|
1462
|
+
vault_name: params.vault_name,
|
|
1463
|
+
metadata: params.metadata
|
|
1464
|
+
};
|
|
1465
|
+
return getSanitizedBody(data);
|
|
1466
|
+
}
|
|
1467
|
+
function NewPromoFundsRequest(params) {
|
|
1468
|
+
const data = {
|
|
1469
|
+
amount: params.amount,
|
|
1470
|
+
external_id: params.external_id,
|
|
1471
|
+
description: params.description,
|
|
1472
|
+
metadata: params.metadata
|
|
1473
|
+
};
|
|
1474
|
+
return getSanitizedBody(data);
|
|
1475
|
+
}
|
|
1476
|
+
function NewAccountRequest(params) {
|
|
1477
|
+
const data = {
|
|
1478
|
+
external_id: params.external_id,
|
|
1479
|
+
account_type: params.account_type,
|
|
1480
|
+
profile: params.profile,
|
|
1481
|
+
information: params.information,
|
|
1482
|
+
primary_address: params.primary_address,
|
|
1483
|
+
business_account_uuid: params.business_account_uuid,
|
|
1484
|
+
parent_account_uuid: params.parent_account_uuid,
|
|
1485
|
+
metadata: params.metadata
|
|
1486
|
+
};
|
|
1487
|
+
return getSanitizedBody(data);
|
|
1488
|
+
}
|
|
1489
|
+
function NewAddressRequest(params) {
|
|
1490
|
+
const data = {
|
|
1491
|
+
label: params.label,
|
|
1492
|
+
line_1: params.line_1,
|
|
1493
|
+
line_2: params.line_2,
|
|
1494
|
+
city: params.city,
|
|
1495
|
+
state: params.state,
|
|
1496
|
+
postal_code: params.postal_code,
|
|
1497
|
+
country: params.country,
|
|
1498
|
+
primary: params.primary,
|
|
1499
|
+
external_id: params.external_id
|
|
1500
|
+
};
|
|
1501
|
+
return getSanitizedBody(data);
|
|
1502
|
+
}
|
|
1503
|
+
function NewGenerateMobileTokenRequest(params) {
|
|
1504
|
+
const data = {
|
|
1505
|
+
account_uuid: params.account_uuid,
|
|
1506
|
+
with_plaid_sdk_token: params.with_plaid_sdk_token
|
|
1507
|
+
};
|
|
1508
|
+
return getSanitizedBody(data);
|
|
1509
|
+
}
|
|
1510
|
+
function NewGeneratePlaidTokenRequest(params) {
|
|
1511
|
+
const data = {
|
|
1512
|
+
account_uuid: params.account_uuid,
|
|
1513
|
+
platform: params.platform,
|
|
1514
|
+
payment_method_uuid: params.payment_method_uuid
|
|
1515
|
+
};
|
|
1516
|
+
return getSanitizedBody(data);
|
|
1517
|
+
}
|
|
1518
|
+
function NewGenerateWebSessionRequest(params) {
|
|
1519
|
+
const data = {
|
|
1520
|
+
account_uuid: params.account_uuid
|
|
1521
|
+
};
|
|
1522
|
+
return getSanitizedBody(data);
|
|
1523
|
+
}
|
|
1524
|
+
function NewGeneratePdsTokenRequest(params) {
|
|
1525
|
+
const data = {
|
|
1526
|
+
wallet_uuid: params.wallet_uuid
|
|
1527
|
+
};
|
|
1528
|
+
return getSanitizedBody(data);
|
|
1529
|
+
}
|
|
1530
|
+
function NewGenerateScopedTokenRequest(params) {
|
|
1531
|
+
return {
|
|
1532
|
+
account_uuid: params.account_uuid
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
function NewDossierRequest(params) {
|
|
1536
|
+
const data = {
|
|
1537
|
+
external_id: params.external_id,
|
|
1538
|
+
documents: params.documents,
|
|
1539
|
+
metadata: params.metadata
|
|
1540
|
+
};
|
|
1541
|
+
return getSanitizedBody(data);
|
|
1542
|
+
}
|
|
1543
|
+
function NewDossierReplaceRequest(params) {
|
|
1544
|
+
const data = {
|
|
1545
|
+
external_id: params.external_id,
|
|
1546
|
+
documents: params.documents,
|
|
1547
|
+
metadata: params.metadata
|
|
1548
|
+
};
|
|
1549
|
+
return getSanitizedBody(data);
|
|
1550
|
+
}
|
|
1551
|
+
class BaseFieldPlugin {
|
|
1552
|
+
constructor() {
|
|
1553
|
+
this.eventListeners = /* @__PURE__ */ new Map();
|
|
1554
|
+
this.configs = /* @__PURE__ */ new Map();
|
|
1555
|
+
}
|
|
1556
|
+
/**
|
|
1557
|
+
* Default sanitization - remove leading/trailing whitespace
|
|
1558
|
+
*/
|
|
1559
|
+
sanitize(value, config) {
|
|
1560
|
+
return value.trim();
|
|
1561
|
+
}
|
|
1562
|
+
/**
|
|
1563
|
+
* Default raw value - just remove formatting spaces
|
|
1564
|
+
*/
|
|
1565
|
+
getRawValue(formattedValue, config) {
|
|
1566
|
+
return formattedValue.replace(/\s/g, "");
|
|
1567
|
+
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Mount plugin to DOM element with event listeners
|
|
1570
|
+
*/
|
|
1571
|
+
mount(element, config) {
|
|
1572
|
+
const finalConfig = { ...this.getDefaultConfig(), ...config };
|
|
1573
|
+
this.configs.set(element, finalConfig);
|
|
1574
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
1575
|
+
this.eventListeners.set(element, listeners);
|
|
1576
|
+
if (element instanceof HTMLInputElement) {
|
|
1577
|
+
this.setupInputElement(element, finalConfig);
|
|
1578
|
+
}
|
|
1579
|
+
this.onMount?.(element, finalConfig);
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Unmount plugin from DOM element
|
|
1583
|
+
*/
|
|
1584
|
+
unmount(element) {
|
|
1585
|
+
const listeners = this.eventListeners.get(element);
|
|
1586
|
+
if (listeners) {
|
|
1587
|
+
listeners.forEach((listener, event) => {
|
|
1588
|
+
element.removeEventListener(event, listener);
|
|
1589
|
+
});
|
|
1590
|
+
this.eventListeners.delete(element);
|
|
1591
|
+
}
|
|
1592
|
+
this.configs.delete(element);
|
|
1593
|
+
this.onUnmount?.(element);
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Setup event listeners for input elements
|
|
1597
|
+
*/
|
|
1598
|
+
setupInputElement(input, config) {
|
|
1599
|
+
const listeners = this.eventListeners.get(input);
|
|
1600
|
+
if (config.formatOnType) {
|
|
1601
|
+
const inputListener = (e) => {
|
|
1602
|
+
const target = e.target;
|
|
1603
|
+
const result = this.format(target.value, config);
|
|
1604
|
+
if (result.formattedValue !== target.value) {
|
|
1605
|
+
target.value = result.formattedValue;
|
|
1606
|
+
if (result.cursorPosition !== void 0) {
|
|
1607
|
+
target.setSelectionRange(result.cursorPosition, result.cursorPosition);
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
input.addEventListener("input", inputListener);
|
|
1612
|
+
listeners.set("input", inputListener);
|
|
1613
|
+
}
|
|
1614
|
+
if (config.validateOnBlur) {
|
|
1615
|
+
const blurListener = (e) => {
|
|
1616
|
+
const target = e.target;
|
|
1617
|
+
const validation = this.validate(target.value, config);
|
|
1618
|
+
this.displayValidation(target, validation, config);
|
|
1619
|
+
};
|
|
1620
|
+
input.addEventListener("blur", blurListener);
|
|
1621
|
+
listeners.set("blur", blurListener);
|
|
1622
|
+
}
|
|
1623
|
+
if (config.validateOnType) {
|
|
1624
|
+
const validateListener = (e) => {
|
|
1625
|
+
const target = e.target;
|
|
1626
|
+
const validation = this.validate(target.value, config);
|
|
1627
|
+
this.displayValidation(target, validation, config);
|
|
1628
|
+
};
|
|
1629
|
+
input.addEventListener("input", validateListener);
|
|
1630
|
+
listeners.set("input-validate", validateListener);
|
|
1631
|
+
}
|
|
1632
|
+
if (config.placeholder) input.placeholder = config.placeholder;
|
|
1633
|
+
if (config.maxLength) input.maxLength = config.maxLength;
|
|
1634
|
+
if (config.ariaLabel) input.setAttribute("aria-label", config.ariaLabel);
|
|
1635
|
+
if (config.ariaDescribedBy) input.setAttribute("aria-describedby", config.ariaDescribedBy);
|
|
1636
|
+
}
|
|
1637
|
+
/**
|
|
1638
|
+
* Display validation results on the element
|
|
1639
|
+
*/
|
|
1640
|
+
displayValidation(element, result, config) {
|
|
1641
|
+
if (!config.showErrorsInline) return;
|
|
1642
|
+
if (config.errorClassName) element.classList.remove(config.errorClassName);
|
|
1643
|
+
if (config.successClassName) element.classList.remove(config.successClassName);
|
|
1644
|
+
if (result.isValid && config.successClassName) {
|
|
1645
|
+
element.classList.add(config.successClassName);
|
|
1646
|
+
} else if (!result.isValid && config.errorClassName) {
|
|
1647
|
+
element.classList.add(config.errorClassName);
|
|
1648
|
+
}
|
|
1649
|
+
this.updateErrorMessage(element, result.error);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Update error message display
|
|
1653
|
+
*/
|
|
1654
|
+
updateErrorMessage(element, error) {
|
|
1655
|
+
let errorElement = element.parentElement?.querySelector(".alviere-field-error");
|
|
1656
|
+
if (error) {
|
|
1657
|
+
if (!errorElement) {
|
|
1658
|
+
errorElement = document.createElement("div");
|
|
1659
|
+
errorElement.className = "alviere-field-error";
|
|
1660
|
+
element.parentElement?.appendChild(errorElement);
|
|
1661
|
+
}
|
|
1662
|
+
errorElement.textContent = error;
|
|
1663
|
+
errorElement.style.display = "block";
|
|
1664
|
+
} else if (errorElement) {
|
|
1665
|
+
errorElement.style.display = "none";
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Get default configuration for this plugin
|
|
1670
|
+
*/
|
|
1671
|
+
getDefaultConfig() {
|
|
1672
|
+
return {
|
|
1673
|
+
validateOnBlur: true,
|
|
1674
|
+
formatOnType: true,
|
|
1675
|
+
showErrorsInline: true,
|
|
1676
|
+
errorClassName: "alviere-field-error",
|
|
1677
|
+
successClassName: "alviere-field-success"
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
const CARD_TYPES = {
|
|
1682
|
+
visa: {
|
|
1683
|
+
name: "Visa",
|
|
1684
|
+
pattern: /^4/,
|
|
1685
|
+
gaps: [4, 8, 12],
|
|
1686
|
+
lengths: [13, 16, 19],
|
|
1687
|
+
code: { name: "CVV", size: 3 }
|
|
1688
|
+
},
|
|
1689
|
+
mastercard: {
|
|
1690
|
+
name: "Mastercard",
|
|
1691
|
+
pattern: /^(5[1-5][0-9]{2}|222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)/,
|
|
1692
|
+
gaps: [4, 8, 12],
|
|
1693
|
+
lengths: [16],
|
|
1694
|
+
code: { name: "CVC", size: 3 }
|
|
1695
|
+
},
|
|
1696
|
+
amex: {
|
|
1697
|
+
name: "American Express",
|
|
1698
|
+
pattern: /^3[47]/,
|
|
1699
|
+
gaps: [4, 10],
|
|
1700
|
+
lengths: [15],
|
|
1701
|
+
code: { name: "CID", size: 4 }
|
|
1702
|
+
},
|
|
1703
|
+
diners: {
|
|
1704
|
+
name: "Diners Club",
|
|
1705
|
+
pattern: /^3[0689]/,
|
|
1706
|
+
gaps: [4, 10],
|
|
1707
|
+
lengths: [14],
|
|
1708
|
+
code: { name: "CVV", size: 3 }
|
|
1709
|
+
},
|
|
1710
|
+
discover: {
|
|
1711
|
+
name: "Discover",
|
|
1712
|
+
pattern: /^6([045]|22)/,
|
|
1713
|
+
gaps: [4, 8, 12],
|
|
1714
|
+
lengths: [16, 19],
|
|
1715
|
+
code: { name: "CID", size: 3 }
|
|
1716
|
+
},
|
|
1717
|
+
jcb: {
|
|
1718
|
+
name: "JCB",
|
|
1719
|
+
pattern: /^35/,
|
|
1720
|
+
gaps: [4, 8, 12],
|
|
1721
|
+
lengths: [16],
|
|
1722
|
+
code: { name: "CVV", size: 3 }
|
|
1723
|
+
}
|
|
1724
|
+
};
|
|
1725
|
+
class CardNumberPlugin extends BaseFieldPlugin {
|
|
1726
|
+
constructor() {
|
|
1727
|
+
super(...arguments);
|
|
1728
|
+
this.name = "card-number";
|
|
1729
|
+
this.version = "1.0.0";
|
|
1730
|
+
this.description = "Credit card number formatting and validation";
|
|
1731
|
+
this.currentCardType = null;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Validate card number using Luhn algorithm and card type patterns
|
|
1735
|
+
*/
|
|
1736
|
+
validate(value, config) {
|
|
1737
|
+
const sanitized = this.sanitize(value);
|
|
1738
|
+
if (!sanitized) {
|
|
1739
|
+
return { isValid: false, error: "Card number is required" };
|
|
1740
|
+
}
|
|
1741
|
+
if (!/^\d+$/.test(sanitized)) {
|
|
1742
|
+
return { isValid: false, error: "Card number must contain only digits" };
|
|
1743
|
+
}
|
|
1744
|
+
const cardType = this.detectCardType(sanitized);
|
|
1745
|
+
this.currentCardType = cardType;
|
|
1746
|
+
if (!cardType) {
|
|
1747
|
+
return { isValid: false, error: "Invalid card number format" };
|
|
1748
|
+
}
|
|
1749
|
+
if (!cardType.lengths.includes(sanitized.length)) {
|
|
1750
|
+
const expectedLengths = cardType.lengths.join(" or ");
|
|
1751
|
+
return {
|
|
1752
|
+
isValid: false,
|
|
1753
|
+
error: `${cardType.name} card numbers must be ${expectedLengths} digits long`
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
if (!this.luhnCheck(sanitized)) {
|
|
1757
|
+
return { isValid: false, error: "Invalid card number" };
|
|
1758
|
+
}
|
|
1759
|
+
if (config?.customValidation) {
|
|
1760
|
+
const customError = config.customValidation(value);
|
|
1761
|
+
if (customError) {
|
|
1762
|
+
return { isValid: false, error: customError };
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
return {
|
|
1766
|
+
isValid: true,
|
|
1767
|
+
suggestions: [`${cardType.name} ending in ${sanitized.slice(-4)}`]
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* Format card number with appropriate spacing based on card type
|
|
1772
|
+
*/
|
|
1773
|
+
format(value, config) {
|
|
1774
|
+
const sanitized = this.sanitize(value);
|
|
1775
|
+
const cardType = this.detectCardType(sanitized) || CARD_TYPES.visa;
|
|
1776
|
+
let formatted = "";
|
|
1777
|
+
let spacesAdded = 0;
|
|
1778
|
+
for (let i = 0; i < sanitized.length; i++) {
|
|
1779
|
+
if (cardType.gaps.includes(i)) {
|
|
1780
|
+
formatted += " ";
|
|
1781
|
+
spacesAdded++;
|
|
1782
|
+
}
|
|
1783
|
+
formatted += sanitized[i];
|
|
1784
|
+
}
|
|
1785
|
+
const cursorPosition = sanitized.length + spacesAdded;
|
|
1786
|
+
return {
|
|
1787
|
+
formattedValue: formatted,
|
|
1788
|
+
cursorPosition
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Remove all non-digit characters
|
|
1793
|
+
*/
|
|
1794
|
+
sanitize(value, config) {
|
|
1795
|
+
return value.replace(/\D/g, "");
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Get raw value without formatting spaces
|
|
1799
|
+
*/
|
|
1800
|
+
getRawValue(formattedValue, config) {
|
|
1801
|
+
return this.sanitize(formattedValue);
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Check if this plugin can handle the field type
|
|
1805
|
+
*/
|
|
1806
|
+
canHandle(fieldType) {
|
|
1807
|
+
return ["card-number", "cardNumber", "pan", "credit-card"].includes(fieldType);
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Get plugin-specific default configuration
|
|
1811
|
+
*/
|
|
1812
|
+
getDefaultConfig() {
|
|
1813
|
+
return {
|
|
1814
|
+
...super.getDefaultConfig(),
|
|
1815
|
+
validateOnType: true,
|
|
1816
|
+
formatOnType: true,
|
|
1817
|
+
placeholder: "1234 5678 9012 3456",
|
|
1818
|
+
maxLength: 23,
|
|
1819
|
+
// Max formatted length for 19-digit cards with spaces
|
|
1820
|
+
ariaLabel: "Credit card number"
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Detect card type based on number pattern
|
|
1825
|
+
*/
|
|
1826
|
+
detectCardType(cardNumber) {
|
|
1827
|
+
for (const [key, cardType] of Object.entries(CARD_TYPES)) {
|
|
1828
|
+
if (cardType.pattern.test(cardNumber)) {
|
|
1829
|
+
return cardType;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Get the detected card type
|
|
1836
|
+
*/
|
|
1837
|
+
getCardType() {
|
|
1838
|
+
return this.currentCardType;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Validate card number using Luhn algorithm
|
|
1842
|
+
*/
|
|
1843
|
+
luhnCheck(cardNumber) {
|
|
1844
|
+
let sum = 0;
|
|
1845
|
+
let isEven = false;
|
|
1846
|
+
for (let i = cardNumber.length - 1; i >= 0; i--) {
|
|
1847
|
+
let digit = parseInt(cardNumber.charAt(i), 10);
|
|
1848
|
+
if (isEven) {
|
|
1849
|
+
digit *= 2;
|
|
1850
|
+
if (digit > 9) {
|
|
1851
|
+
digit -= 9;
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
sum += digit;
|
|
1855
|
+
isEven = !isEven;
|
|
1856
|
+
}
|
|
1857
|
+
return sum % 10 === 0;
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Enhanced mount with card type detection
|
|
1861
|
+
*/
|
|
1862
|
+
onMount(element, config) {
|
|
1863
|
+
if (element instanceof HTMLInputElement) {
|
|
1864
|
+
const cardTypeListener = (e) => {
|
|
1865
|
+
const target = e.target;
|
|
1866
|
+
const sanitized = this.sanitize(target.value);
|
|
1867
|
+
const cardType = this.detectCardType(sanitized);
|
|
1868
|
+
if (cardType !== this.currentCardType) {
|
|
1869
|
+
this.currentCardType = cardType;
|
|
1870
|
+
element.dispatchEvent(new CustomEvent("cardTypeChange", {
|
|
1871
|
+
detail: { cardType, element },
|
|
1872
|
+
bubbles: true
|
|
1873
|
+
// Allow event to bubble up to document
|
|
1874
|
+
}));
|
|
1875
|
+
}
|
|
1876
|
+
if (cardType) {
|
|
1877
|
+
const maxFormattedLength = Math.max(...cardType.lengths) + cardType.gaps.length;
|
|
1878
|
+
target.maxLength = maxFormattedLength;
|
|
1879
|
+
}
|
|
1880
|
+
};
|
|
1881
|
+
element.addEventListener("input", cardTypeListener);
|
|
1882
|
+
const listeners = this.eventListeners.get(element);
|
|
1883
|
+
if (listeners) {
|
|
1884
|
+
listeners.set("cardTypeDetection", cardTypeListener);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
class FieldPluginRegistry {
|
|
1890
|
+
constructor(config = {}) {
|
|
1891
|
+
this.plugins = /* @__PURE__ */ new Map();
|
|
1892
|
+
this.fieldTypeCache = /* @__PURE__ */ new Map();
|
|
1893
|
+
this.config = {
|
|
1894
|
+
autoRegisterBuiltins: true,
|
|
1895
|
+
enablePluginValidation: true,
|
|
1896
|
+
...config
|
|
1897
|
+
};
|
|
1898
|
+
this.logger = config.logger;
|
|
1899
|
+
if (this.config.autoRegisterBuiltins) {
|
|
1900
|
+
this.registerBuiltinPlugins();
|
|
1901
|
+
}
|
|
1902
|
+
this.logger?.debug("🔐 FieldPluginRegistry initialized");
|
|
1903
|
+
}
|
|
1904
|
+
/**
|
|
1905
|
+
* Register a field plugin
|
|
1906
|
+
*/
|
|
1907
|
+
register(plugin) {
|
|
1908
|
+
if (this.config.enablePluginValidation) {
|
|
1909
|
+
this.validatePlugin(plugin);
|
|
1910
|
+
}
|
|
1911
|
+
this.plugins.set(plugin.name, plugin);
|
|
1912
|
+
this.fieldTypeCache.clear();
|
|
1913
|
+
this.logger?.debug(`FieldPluginRegistry: Registered plugin '${plugin.name}' v${plugin.version}`);
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Unregister a field plugin
|
|
1917
|
+
*/
|
|
1918
|
+
unregister(pluginName) {
|
|
1919
|
+
const removed = this.plugins.delete(pluginName);
|
|
1920
|
+
if (removed) {
|
|
1921
|
+
this.fieldTypeCache.clear();
|
|
1922
|
+
this.logger?.debug(`FieldPluginRegistry: Unregistered plugin '${pluginName}'`);
|
|
1923
|
+
}
|
|
1924
|
+
return removed;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Get a plugin by name
|
|
1928
|
+
*/
|
|
1929
|
+
getPlugin(pluginName) {
|
|
1930
|
+
return this.plugins.get(pluginName);
|
|
1931
|
+
}
|
|
1932
|
+
/**
|
|
1933
|
+
* Get plugin for a specific field type (with caching)
|
|
1934
|
+
*/
|
|
1935
|
+
getPluginForFieldType(fieldType) {
|
|
1936
|
+
if (this.fieldTypeCache.has(fieldType)) {
|
|
1937
|
+
return this.fieldTypeCache.get(fieldType);
|
|
1938
|
+
}
|
|
1939
|
+
for (const plugin of this.plugins.values()) {
|
|
1940
|
+
if (plugin.canHandle(fieldType)) {
|
|
1941
|
+
this.fieldTypeCache.set(fieldType, plugin);
|
|
1942
|
+
return plugin;
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
const textPlugin = this.plugins.get("text");
|
|
1946
|
+
if (textPlugin) {
|
|
1947
|
+
this.fieldTypeCache.set(fieldType, textPlugin);
|
|
1948
|
+
return textPlugin;
|
|
1949
|
+
}
|
|
1950
|
+
return void 0;
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Get all registered plugins
|
|
1954
|
+
*/
|
|
1955
|
+
getAllPlugins() {
|
|
1956
|
+
return Array.from(this.plugins.values());
|
|
1957
|
+
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Get plugin names
|
|
1960
|
+
*/
|
|
1961
|
+
getPluginNames() {
|
|
1962
|
+
return Array.from(this.plugins.keys());
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Check if a plugin is registered
|
|
1966
|
+
*/
|
|
1967
|
+
hasPlugin(pluginName) {
|
|
1968
|
+
return this.plugins.has(pluginName);
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Get supported field types across all plugins
|
|
1972
|
+
*/
|
|
1973
|
+
getSupportedFieldTypes() {
|
|
1974
|
+
const fieldTypes = /* @__PURE__ */ new Set();
|
|
1975
|
+
const commonFieldTypes = [
|
|
1976
|
+
"card-number",
|
|
1977
|
+
"cardNumber",
|
|
1978
|
+
"pan",
|
|
1979
|
+
"expiry",
|
|
1980
|
+
"expiry-date",
|
|
1981
|
+
"expiryDate",
|
|
1982
|
+
"cvv",
|
|
1983
|
+
"cvc",
|
|
1984
|
+
"code",
|
|
1985
|
+
"zip",
|
|
1986
|
+
"zip-code",
|
|
1987
|
+
"postal-code",
|
|
1988
|
+
"text",
|
|
1989
|
+
"name",
|
|
1990
|
+
"cardholder-name"
|
|
1991
|
+
];
|
|
1992
|
+
for (const fieldType of commonFieldTypes) {
|
|
1993
|
+
if (this.getPluginForFieldType(fieldType)) {
|
|
1994
|
+
fieldTypes.add(fieldType);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return Array.from(fieldTypes);
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* Create and mount a plugin for an element
|
|
2001
|
+
*/
|
|
2002
|
+
mountPlugin(element, fieldType, config) {
|
|
2003
|
+
const plugin = this.getPluginForFieldType(fieldType);
|
|
2004
|
+
if (!plugin) {
|
|
2005
|
+
this.logger?.warn(`FieldPluginRegistry: No plugin found for field type '${fieldType}'`);
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
try {
|
|
2009
|
+
plugin.mount(element, config);
|
|
2010
|
+
this.logger?.debug(`FieldPluginRegistry: Mounted '${plugin.name}' plugin on element for field type '${fieldType}'`);
|
|
2011
|
+
return plugin;
|
|
2012
|
+
} catch (error) {
|
|
2013
|
+
this.logger?.error(`FieldPluginRegistry: Failed to mount plugin '${plugin.name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
2014
|
+
return null;
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Unmount plugin from an element
|
|
2019
|
+
*/
|
|
2020
|
+
unmountPlugin(element, fieldType) {
|
|
2021
|
+
const plugin = this.getPluginForFieldType(fieldType);
|
|
2022
|
+
if (!plugin) {
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
try {
|
|
2026
|
+
plugin.unmount(element);
|
|
2027
|
+
this.logger?.debug(`FieldPluginRegistry: Unmounted '${plugin.name}' plugin from element`);
|
|
2028
|
+
return true;
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
this.logger?.error(`FieldPluginRegistry: Failed to unmount plugin '${plugin.name}': ${error instanceof Error ? error.message : String(error)}`);
|
|
2031
|
+
return false;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
/**
|
|
2035
|
+
* Get plugin registry statistics
|
|
2036
|
+
*/
|
|
2037
|
+
getStats() {
|
|
2038
|
+
return {
|
|
2039
|
+
totalPlugins: this.plugins.size,
|
|
2040
|
+
pluginNames: this.getPluginNames(),
|
|
2041
|
+
supportedFieldTypes: this.getSupportedFieldTypes(),
|
|
2042
|
+
cacheSize: this.fieldTypeCache.size
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
/**
|
|
2046
|
+
* Clear all caches
|
|
2047
|
+
*/
|
|
2048
|
+
clearCaches() {
|
|
2049
|
+
this.fieldTypeCache.clear();
|
|
2050
|
+
}
|
|
2051
|
+
/**
|
|
2052
|
+
* Register all built-in plugins
|
|
2053
|
+
*
|
|
2054
|
+
* Note: ALL plugins are currently disabled as they have no active consumers.
|
|
2055
|
+
* - ui-svelte implements its own card handling in CardPanInput.svelte
|
|
2056
|
+
* - CardNumberPlugin was only used in its own tests
|
|
2057
|
+
* - Other plugins (ExpiryDatePlugin, CVVPlugin, TextPlugin, ZipCodePlugin) were never activated
|
|
2058
|
+
*
|
|
2059
|
+
* The plugin system remains as a future architecture option if needed.
|
|
2060
|
+
*
|
|
2061
|
+
* To re-enable plugins in the future:
|
|
2062
|
+
* 1. Uncomment the imports at the top of this file
|
|
2063
|
+
* 2. Add plugin instances to the builtinPlugins array below
|
|
2064
|
+
* 3. Ensure tests exist for the plugins
|
|
2065
|
+
* 4. Update vitest.config.ts to remove them from coverage exclusions
|
|
2066
|
+
* 5. Update consuming code to actually use the plugins
|
|
2067
|
+
*/
|
|
2068
|
+
registerBuiltinPlugins() {
|
|
2069
|
+
const builtinPlugins = [
|
|
2070
|
+
// All plugins disabled - no active consumers
|
|
2071
|
+
// new CardNumberPlugin(),
|
|
2072
|
+
// new ExpiryDatePlugin(),
|
|
2073
|
+
// new CVVPlugin(),
|
|
2074
|
+
// new TextPlugin(),
|
|
2075
|
+
// new ZipCodePlugin()
|
|
2076
|
+
];
|
|
2077
|
+
for (const plugin of builtinPlugins) {
|
|
2078
|
+
this.register(plugin);
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
/**
|
|
2082
|
+
* Validate plugin before registration
|
|
2083
|
+
*/
|
|
2084
|
+
validatePlugin(plugin) {
|
|
2085
|
+
if (!plugin.name || typeof plugin.name !== "string") {
|
|
2086
|
+
throw new Error("Plugin must have a valid name");
|
|
2087
|
+
}
|
|
2088
|
+
if (!plugin.version || typeof plugin.version !== "string") {
|
|
2089
|
+
throw new Error("Plugin must have a valid version");
|
|
2090
|
+
}
|
|
2091
|
+
if (typeof plugin.validate !== "function") {
|
|
2092
|
+
throw new Error("Plugin must implement validate method");
|
|
2093
|
+
}
|
|
2094
|
+
if (typeof plugin.format !== "function") {
|
|
2095
|
+
throw new Error("Plugin must implement format method");
|
|
2096
|
+
}
|
|
2097
|
+
if (typeof plugin.sanitize !== "function") {
|
|
2098
|
+
throw new Error("Plugin must implement sanitize method");
|
|
2099
|
+
}
|
|
2100
|
+
if (typeof plugin.canHandle !== "function") {
|
|
2101
|
+
throw new Error("Plugin must implement canHandle method");
|
|
2102
|
+
}
|
|
2103
|
+
if (typeof plugin.mount !== "function") {
|
|
2104
|
+
throw new Error("Plugin must implement mount method");
|
|
2105
|
+
}
|
|
2106
|
+
if (typeof plugin.unmount !== "function") {
|
|
2107
|
+
throw new Error("Plugin must implement unmount method");
|
|
2108
|
+
}
|
|
2109
|
+
if (this.plugins.has(plugin.name)) {
|
|
2110
|
+
this.logger?.warn(`FieldPluginRegistry: Plugin '${plugin.name}' is already registered and will be replaced`);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
}
|
|
2114
|
+
class AlviereCore {
|
|
2115
|
+
constructor(config = {}) {
|
|
2116
|
+
this.authToken = "";
|
|
2117
|
+
this.business_uuid = "";
|
|
2118
|
+
this.account_uuid = "";
|
|
2119
|
+
this.cryptor = null;
|
|
2120
|
+
this.gateways = /* @__PURE__ */ new Map();
|
|
2121
|
+
this.jwt = config.jwt || "";
|
|
2122
|
+
this.debug = config.debug || false;
|
|
2123
|
+
this.publicCertificate = config.publicCertificate || "";
|
|
2124
|
+
this.publicCertificateId = config.publicCertificateId || "";
|
|
2125
|
+
this.business_uuid = config.business_uuid || "";
|
|
2126
|
+
this.logger = new Logger(this.debug);
|
|
2127
|
+
this.logger.debug(`🔐 AlviereCore constructor called with config: ${JSON.stringify(config)}`);
|
|
2128
|
+
if (this.jwt) {
|
|
2129
|
+
const jwtConfig = extractAlviereConfigFromJWT(this.jwt);
|
|
2130
|
+
if (config.jwt) {
|
|
2131
|
+
setRuntimeConfig({
|
|
2132
|
+
...jwtConfig,
|
|
2133
|
+
certificate: { id: this.publicCertificateId, public_key: this.publicCertificate }
|
|
2134
|
+
});
|
|
2135
|
+
}
|
|
2136
|
+
const extractedAccountUuid = extractAccountUuidFromJWT(this.jwt);
|
|
2137
|
+
if (extractedAccountUuid) {
|
|
2138
|
+
this.account_uuid = extractedAccountUuid;
|
|
2139
|
+
this.logger.debug(`🔐 Extracted account_uuid from JWT sub field: ${this.account_uuid}`);
|
|
2140
|
+
} else {
|
|
2141
|
+
this.logger.debug("⚠️ No account_uuid found in JWT sub field");
|
|
2142
|
+
}
|
|
2143
|
+
this.authToken = this.jwt;
|
|
2144
|
+
this.logger.debug("🔐 Using JWT as auth token for API authentication");
|
|
2145
|
+
}
|
|
2146
|
+
if (config.alviere) {
|
|
2147
|
+
setRuntimeConfig(config.alviere);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
// Update configuration
|
|
2151
|
+
configure(config) {
|
|
2152
|
+
const jwtChanged = config.jwt && config.jwt !== this.jwt;
|
|
2153
|
+
if (config.jwt) {
|
|
2154
|
+
this.jwt = config.jwt;
|
|
2155
|
+
const jwtConfig = extractAlviereConfigFromJWT(config.jwt);
|
|
2156
|
+
if (jwtConfig.domain) {
|
|
2157
|
+
setRuntimeConfig(jwtConfig);
|
|
2158
|
+
}
|
|
2159
|
+
const extractedAccountUuid = extractAccountUuidFromJWT(config.jwt);
|
|
2160
|
+
this.logger.debug(`🔐 Extracted account_uuid from JWT sub field: ${extractedAccountUuid}`);
|
|
2161
|
+
if (extractedAccountUuid) {
|
|
2162
|
+
this.account_uuid = extractedAccountUuid;
|
|
2163
|
+
this.logger.debug(`🔐 Updated account_uuid from JWT sub field: ${this.account_uuid}`);
|
|
2164
|
+
} else {
|
|
2165
|
+
this.logger.debug("⚠️ No account_uuid found in new JWT sub field");
|
|
2166
|
+
}
|
|
2167
|
+
this.authToken = this.jwt;
|
|
2168
|
+
this.logger.debug("🔐 Updated JWT as auth token for API authentication");
|
|
2169
|
+
}
|
|
2170
|
+
if (config.business_uuid !== void 0) {
|
|
2171
|
+
this.business_uuid = config.business_uuid;
|
|
2172
|
+
this.logger.debug(`🔐 Updated business_uuid: ${this.business_uuid}`);
|
|
2173
|
+
}
|
|
2174
|
+
if (typeof config.debug === "boolean" && config.debug !== this.debug) {
|
|
2175
|
+
this.debug = config.debug;
|
|
2176
|
+
this.logger = new Logger(this.debug);
|
|
2177
|
+
this.logger.debug("🔐 Debug mode changed to: " + this.debug);
|
|
2178
|
+
}
|
|
2179
|
+
if (jwtChanged) {
|
|
2180
|
+
this.logger.debug("🔐 JWT changed - clearing gateway cache and cryptor");
|
|
2181
|
+
this.gateways.clear();
|
|
2182
|
+
this.cryptor = null;
|
|
2183
|
+
}
|
|
2184
|
+
if (config.alviere) {
|
|
2185
|
+
setRuntimeConfig(config.alviere);
|
|
2186
|
+
}
|
|
2187
|
+
return this;
|
|
2188
|
+
}
|
|
2189
|
+
/**
|
|
2190
|
+
* Get or create Cryptor instance (lazy initialization with validation)
|
|
2191
|
+
* This ensures we only create Cryptor when it's actually needed and we have a valid RSA key
|
|
2192
|
+
* @throws Error if RSA public key is not available
|
|
2193
|
+
*/
|
|
2194
|
+
getOrCreateCryptor() {
|
|
2195
|
+
if (!this.cryptor) {
|
|
2196
|
+
const rsaKey = getRSA_PUB_KEY();
|
|
2197
|
+
if (!rsaKey || rsaKey.trim() === "") {
|
|
2198
|
+
throw new Error(
|
|
2199
|
+
'🔐 Cryptor initialization failed: RSA public key not available. Please ensure JWT is configured with a valid RSA key before attempting encryption operations. Call alviereCore.configure({ jwt: "your-jwt-with-rsa-key" }) first.'
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
this.logger.debug("🔐 Creating Cryptor instance with RSA key");
|
|
2203
|
+
this.cryptor = new Cryptor(this.logger);
|
|
2204
|
+
}
|
|
2205
|
+
return this.cryptor;
|
|
2206
|
+
}
|
|
2207
|
+
// Create payment gateway (cached and reused)
|
|
2208
|
+
createPaymentsGateway() {
|
|
2209
|
+
if (!this.gateways.has("payments")) {
|
|
2210
|
+
this.gateways.set(
|
|
2211
|
+
"payments",
|
|
2212
|
+
new PaymentsGateway(this.authToken, this.getOrCreateCryptor(), this.logger)
|
|
2213
|
+
);
|
|
2214
|
+
}
|
|
2215
|
+
return this.gateways.get("payments");
|
|
2216
|
+
}
|
|
2217
|
+
// Create payments service (reuses cached gateway and shared logger)
|
|
2218
|
+
createPaymentsService() {
|
|
2219
|
+
const gateway = this.createPaymentsGateway();
|
|
2220
|
+
return new PaymentProcessor(gateway, this.logger);
|
|
2221
|
+
}
|
|
2222
|
+
// Create wallet services (reuses cached gateway)
|
|
2223
|
+
createWalletsService() {
|
|
2224
|
+
if (!this.gateways.has("wallets")) {
|
|
2225
|
+
this.gateways.set(
|
|
2226
|
+
"wallets",
|
|
2227
|
+
new WalletsGateway(this.authToken, this.getOrCreateCryptor(), this.logger)
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
return new WalletsApiService(this.gateways.get("wallets"));
|
|
2231
|
+
}
|
|
2232
|
+
// Create payment instruments service (reuses cached gateway)
|
|
2233
|
+
createPaymentInstrumentsService() {
|
|
2234
|
+
if (!this.gateways.has("paymentInstruments")) {
|
|
2235
|
+
this.gateways.set(
|
|
2236
|
+
"paymentInstruments",
|
|
2237
|
+
new PaymentInstrumentsGateway(this.authToken, this.getOrCreateCryptor(), this.logger)
|
|
2238
|
+
);
|
|
2239
|
+
}
|
|
2240
|
+
return new PaymentInstrumentsApiService(this.gateways.get("paymentInstruments"));
|
|
2241
|
+
}
|
|
2242
|
+
// Create accounts service (reuses cached gateway)
|
|
2243
|
+
createAccountsService() {
|
|
2244
|
+
if (!this.gateways.has("accounts")) {
|
|
2245
|
+
this.gateways.set(
|
|
2246
|
+
"accounts",
|
|
2247
|
+
new AccountsGateway(this.authToken, this.getOrCreateCryptor(), this.logger)
|
|
2248
|
+
);
|
|
2249
|
+
}
|
|
2250
|
+
return new AccountsApiService(this.gateways.get("accounts"));
|
|
2251
|
+
}
|
|
2252
|
+
// Create auth gateway (reuses cached gateway)
|
|
2253
|
+
createAuthGateway() {
|
|
2254
|
+
if (!this.gateways.has("auth")) {
|
|
2255
|
+
if (!this.cryptor) {
|
|
2256
|
+
throw new Error("Cryptor not initialized. Cannot create AuthGateway.");
|
|
2257
|
+
}
|
|
2258
|
+
this.gateways.set("auth", new AuthGateway(this.authToken, this.cryptor, this.logger));
|
|
2259
|
+
}
|
|
2260
|
+
return this.gateways.get("auth");
|
|
2261
|
+
}
|
|
2262
|
+
/**
|
|
2263
|
+
* Generate scoped JWT for specific account (JWT downgrade)
|
|
2264
|
+
* Exchanges the current business-level JWT for a consumer-level JWT
|
|
2265
|
+
*
|
|
2266
|
+
* @param accountUuid - The account UUID to scope the JWT to
|
|
2267
|
+
* @param autoUpdate - If true, automatically updates the SDK with the new JWT (default: true)
|
|
2268
|
+
* @returns The new scoped JWT token
|
|
2269
|
+
*/
|
|
2270
|
+
async generateScopedToken(accountUuid, autoUpdate = true) {
|
|
2271
|
+
this.logger.debug(`🔐 Generating scoped token for account: ${accountUuid}`);
|
|
2272
|
+
this.logger.debug(`🔐 Auto-update: ${autoUpdate}`);
|
|
2273
|
+
const authGateway = this.createAuthGateway();
|
|
2274
|
+
const response = await authGateway.generateScopedToken({ account_uuid: accountUuid });
|
|
2275
|
+
this.logger.debug("✅ Scoped token generated successfully");
|
|
2276
|
+
if (autoUpdate && response.access_token) {
|
|
2277
|
+
this.logger.debug("🔄 Auto-updating SDK with new scoped token");
|
|
2278
|
+
this.configure({ jwt: response.access_token });
|
|
2279
|
+
}
|
|
2280
|
+
return response.access_token;
|
|
2281
|
+
}
|
|
2282
|
+
// Get shared instances (for advanced usage)
|
|
2283
|
+
getLogger() {
|
|
2284
|
+
return this.logger;
|
|
2285
|
+
}
|
|
2286
|
+
/**
|
|
2287
|
+
* Get Cryptor instance (lazy initialized with validation)
|
|
2288
|
+
* @throws Error if RSA public key is not available
|
|
2289
|
+
*/
|
|
2290
|
+
getCryptor() {
|
|
2291
|
+
return this.getOrCreateCryptor();
|
|
2292
|
+
}
|
|
2293
|
+
// Clear gateway cache (useful for testing or manual cache invalidation)
|
|
2294
|
+
clearCache() {
|
|
2295
|
+
this.gateways.clear();
|
|
2296
|
+
}
|
|
2297
|
+
/**
|
|
2298
|
+
* Get the account UUID extracted from JWT's sub field
|
|
2299
|
+
* @returns The account UUID, or empty string if not available
|
|
2300
|
+
*/
|
|
2301
|
+
getAccountUuid() {
|
|
2302
|
+
return this.account_uuid;
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2305
|
+
* Get the business UUID from configuration
|
|
2306
|
+
* @returns The business UUID, or empty string if not configured
|
|
2307
|
+
*/
|
|
2308
|
+
getBusinessUuid() {
|
|
2309
|
+
return this.business_uuid;
|
|
2310
|
+
}
|
|
2311
|
+
/**
|
|
2312
|
+
* Determine if the user is accessing the SDK for the first time
|
|
2313
|
+
* Returns true if account_uuid matches business_uuid (user authenticated as business)
|
|
2314
|
+
* Returns false if they differ (JWT already scoped to a specific payee/consumer)
|
|
2315
|
+
*
|
|
2316
|
+
* @returns true if first-time user, false otherwise
|
|
2317
|
+
*/
|
|
2318
|
+
isFirstTimeUser() {
|
|
2319
|
+
if (!this.account_uuid || !this.business_uuid) {
|
|
2320
|
+
this.logger.debug(
|
|
2321
|
+
"🔐 Cannot determine first-time user status: missing account_uuid or business_uuid"
|
|
2322
|
+
);
|
|
2323
|
+
return false;
|
|
2324
|
+
}
|
|
2325
|
+
const isFirstTime = this.account_uuid === this.business_uuid;
|
|
2326
|
+
this.logger.debug(
|
|
2327
|
+
`🔐 First-time user check: ${isFirstTime} (account: ${this.account_uuid}, business: ${this.business_uuid})`
|
|
2328
|
+
);
|
|
2329
|
+
return isFirstTime;
|
|
2330
|
+
}
|
|
2331
|
+
/**
|
|
2332
|
+
* Determine if the JWT is scoped to a specific payee/consumer
|
|
2333
|
+
* Returns true if account_uuid differs from business_uuid
|
|
2334
|
+
* Returns false if they match or if either is missing
|
|
2335
|
+
*
|
|
2336
|
+
* @returns true if scoped to payee, false otherwise
|
|
2337
|
+
*/
|
|
2338
|
+
isScopedToPayee() {
|
|
2339
|
+
if (!this.account_uuid || !this.business_uuid) {
|
|
2340
|
+
return false;
|
|
2341
|
+
}
|
|
2342
|
+
return this.account_uuid !== this.business_uuid;
|
|
2343
|
+
}
|
|
2344
|
+
// Static utility methods
|
|
2345
|
+
static createValidator(debug = false) {
|
|
2346
|
+
const logger = new Logger(debug);
|
|
2347
|
+
return new Validator(logger);
|
|
2348
|
+
}
|
|
2349
|
+
static createLogger(debug = false) {
|
|
2350
|
+
return new Logger(debug);
|
|
2351
|
+
}
|
|
2352
|
+
static createCryptor(logger) {
|
|
2353
|
+
return new Cryptor(logger);
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
export {
|
|
2357
|
+
ALVIERE_DOMAIN,
|
|
2358
|
+
AccountsApiService,
|
|
2359
|
+
AccountsGateway,
|
|
2360
|
+
AlcoreApiError,
|
|
2361
|
+
AlcoreErrorCodes,
|
|
2362
|
+
AlcoreErrorMessages,
|
|
2363
|
+
AlviereCore,
|
|
2364
|
+
AuthGateway,
|
|
2365
|
+
BaseFieldPlugin,
|
|
2366
|
+
CERTIFICATE_ID,
|
|
2367
|
+
CardNumberPlugin,
|
|
2368
|
+
CriticalErrorCodes,
|
|
2369
|
+
Cryptor,
|
|
2370
|
+
DEFAULT_CONFIG,
|
|
2371
|
+
FieldPluginRegistry,
|
|
2372
|
+
Logger,
|
|
2373
|
+
NewAccountRequest,
|
|
2374
|
+
NewAddBankAccountRequest,
|
|
2375
|
+
NewAddCardRequest,
|
|
2376
|
+
NewAddressRequest,
|
|
2377
|
+
NewCardInstrumentFromForm,
|
|
2378
|
+
NewCreditFundsRequest,
|
|
2379
|
+
NewDebitFundsRequest,
|
|
2380
|
+
NewDossierReplaceRequest,
|
|
2381
|
+
NewDossierRequest,
|
|
2382
|
+
NewGenerateMobileTokenRequest,
|
|
2383
|
+
NewGeneratePdsTokenRequest,
|
|
2384
|
+
NewGeneratePlaidTokenRequest,
|
|
2385
|
+
NewGenerateScopedTokenRequest,
|
|
2386
|
+
NewGenerateWebSessionRequest,
|
|
2387
|
+
NewLoadFundsRequest,
|
|
2388
|
+
NewPaymentInstrumentRequest,
|
|
2389
|
+
NewPromoFundsRequest,
|
|
2390
|
+
NewSendFundsRequest,
|
|
2391
|
+
NewTransferFundsRequest,
|
|
2392
|
+
NewWithdrawFundsRequest,
|
|
2393
|
+
PaymentInstrumentsApiService,
|
|
2394
|
+
PaymentInstrumentsGateway,
|
|
2395
|
+
PaymentProcessor,
|
|
2396
|
+
PaymentsGateway,
|
|
2397
|
+
RSA_PUB_KEY,
|
|
2398
|
+
Validator,
|
|
2399
|
+
WalletsApiService,
|
|
2400
|
+
WalletsGateway,
|
|
2401
|
+
createDefaultMerchantDetails,
|
|
2402
|
+
decodeJWTPayload,
|
|
2403
|
+
AlviereCore as default,
|
|
2404
|
+
extractAccountUuidFromJWT,
|
|
2405
|
+
extractAlviereConfigFromJWT,
|
|
2406
|
+
getALVIERE_DOMAIN,
|
|
2407
|
+
getCERTIFICATE_ID,
|
|
2408
|
+
getErrorMessage,
|
|
2409
|
+
getJWTExpiration,
|
|
2410
|
+
getRSA_PUB_KEY,
|
|
2411
|
+
getRuntimeConfig,
|
|
2412
|
+
getSanitizedBody,
|
|
2413
|
+
initializeFromEnvironment,
|
|
2414
|
+
isCriticalError,
|
|
2415
|
+
isJWTExpired,
|
|
2416
|
+
setRuntimeConfig,
|
|
2417
|
+
throwIfApiError
|
|
2418
|
+
};
|
|
2419
|
+
//# sourceMappingURL=index.mjs.map
|