@blocklet/payment-broker-client 1.20.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/examples/README.md +277 -0
  2. package/examples/browser/README.md +119 -0
  3. package/examples/browser/simple-example.js +223 -0
  4. package/examples/nodejs/README.md +180 -0
  5. package/examples/nodejs/deploy-example.mjs +200 -0
  6. package/examples/nodejs/package.json +30 -0
  7. package/lib/adapters/config/browser.d.ts +12 -0
  8. package/lib/adapters/config/browser.d.ts.map +1 -0
  9. package/lib/adapters/config/browser.js +48 -0
  10. package/lib/adapters/config/browser.js.map +1 -0
  11. package/lib/adapters/config/node.d.ts +13 -0
  12. package/lib/adapters/config/node.d.ts.map +1 -0
  13. package/lib/adapters/config/node.js +62 -0
  14. package/lib/adapters/config/node.js.map +1 -0
  15. package/lib/adapters/http/browser.d.ts +12 -0
  16. package/lib/adapters/http/browser.d.ts.map +1 -0
  17. package/lib/adapters/http/browser.js +50 -0
  18. package/lib/adapters/http/browser.js.map +1 -0
  19. package/lib/adapters/http/node.d.ts +12 -0
  20. package/lib/adapters/http/node.d.ts.map +1 -0
  21. package/lib/adapters/http/node.js +30 -0
  22. package/lib/adapters/http/node.js.map +1 -0
  23. package/lib/browser/adapters/config/browser.d.ts +12 -0
  24. package/lib/browser/adapters/config/browser.d.ts.map +1 -0
  25. package/lib/browser/adapters/config/node.d.ts +13 -0
  26. package/lib/browser/adapters/config/node.d.ts.map +1 -0
  27. package/lib/browser/adapters/http/browser.d.ts +12 -0
  28. package/lib/browser/adapters/http/browser.d.ts.map +1 -0
  29. package/lib/browser/adapters/http/node.d.ts +12 -0
  30. package/lib/browser/adapters/http/node.d.ts.map +1 -0
  31. package/lib/browser/browser/factory.d.ts +19 -0
  32. package/lib/browser/browser/factory.d.ts.map +1 -0
  33. package/lib/browser/browser/index.d.ts +9 -0
  34. package/lib/browser/browser/index.d.ts.map +1 -0
  35. package/lib/browser/core/client.d.ts +29 -0
  36. package/lib/browser/core/client.d.ts.map +1 -0
  37. package/lib/browser/core/deployment.d.ts +20 -0
  38. package/lib/browser/core/deployment.d.ts.map +1 -0
  39. package/lib/browser/core/errors.d.ts +15 -0
  40. package/lib/browser/core/errors.d.ts.map +1 -0
  41. package/lib/browser/core/index.d.ts +10 -0
  42. package/lib/browser/core/index.d.ts.map +1 -0
  43. package/lib/browser/core/interfaces.d.ts +24 -0
  44. package/lib/browser/core/interfaces.d.ts.map +1 -0
  45. package/lib/browser/core/logger.d.ts +8 -0
  46. package/lib/browser/core/logger.d.ts.map +1 -0
  47. package/lib/browser/core/polling.d.ts +23 -0
  48. package/lib/browser/core/polling.d.ts.map +1 -0
  49. package/lib/browser/core/session.d.ts +31 -0
  50. package/lib/browser/core/session.d.ts.map +1 -0
  51. package/lib/browser/core/types.d.ts +175 -0
  52. package/lib/browser/core/types.d.ts.map +1 -0
  53. package/lib/browser/core/utils.d.ts +27 -0
  54. package/lib/browser/core/utils.d.ts.map +1 -0
  55. package/lib/browser/factory.d.ts +19 -0
  56. package/lib/browser/factory.d.ts.map +1 -0
  57. package/lib/browser/factory.js +30 -0
  58. package/lib/browser/factory.js.map +1 -0
  59. package/lib/browser/index.d.ts +8 -0
  60. package/lib/browser/index.d.ts.map +1 -0
  61. package/lib/browser/index.js +973 -0
  62. package/lib/browser/index.js.map +1 -0
  63. package/lib/browser/node/factory.d.ts +9 -0
  64. package/lib/browser/node/factory.d.ts.map +1 -0
  65. package/lib/browser/node/index.d.ts +8 -0
  66. package/lib/browser/node/index.d.ts.map +1 -0
  67. package/lib/core/client.d.ts +29 -0
  68. package/lib/core/client.d.ts.map +1 -0
  69. package/lib/core/client.js +150 -0
  70. package/lib/core/client.js.map +1 -0
  71. package/lib/core/deployment.d.ts +20 -0
  72. package/lib/core/deployment.d.ts.map +1 -0
  73. package/lib/core/deployment.js +184 -0
  74. package/lib/core/deployment.js.map +1 -0
  75. package/lib/core/errors.d.ts +15 -0
  76. package/lib/core/errors.d.ts.map +1 -0
  77. package/lib/core/errors.js +21 -0
  78. package/lib/core/errors.js.map +1 -0
  79. package/lib/core/index.d.ts +10 -0
  80. package/lib/core/index.d.ts.map +1 -0
  81. package/lib/core/index.js +32 -0
  82. package/lib/core/index.js.map +1 -0
  83. package/lib/core/interfaces.d.ts +24 -0
  84. package/lib/core/interfaces.d.ts.map +1 -0
  85. package/lib/core/interfaces.js +4 -0
  86. package/lib/core/interfaces.js.map +1 -0
  87. package/lib/core/logger.d.ts +8 -0
  88. package/lib/core/logger.d.ts.map +1 -0
  89. package/lib/core/logger.js +39 -0
  90. package/lib/core/logger.js.map +1 -0
  91. package/lib/core/polling.d.ts +23 -0
  92. package/lib/core/polling.d.ts.map +1 -0
  93. package/lib/core/polling.js +97 -0
  94. package/lib/core/polling.js.map +1 -0
  95. package/lib/core/session.d.ts +31 -0
  96. package/lib/core/session.d.ts.map +1 -0
  97. package/lib/core/session.js +202 -0
  98. package/lib/core/session.js.map +1 -0
  99. package/lib/core/types.d.ts +175 -0
  100. package/lib/core/types.d.ts.map +1 -0
  101. package/lib/core/types.js +48 -0
  102. package/lib/core/types.js.map +1 -0
  103. package/lib/core/utils.d.ts +27 -0
  104. package/lib/core/utils.d.ts.map +1 -0
  105. package/lib/core/utils.js +181 -0
  106. package/lib/core/utils.js.map +1 -0
  107. package/lib/index.d.ts +484 -0
  108. package/lib/index.d.ts.map +1 -0
  109. package/lib/index.esm.js +969 -0
  110. package/lib/index.esm.js.map +1 -0
  111. package/lib/index.js +984 -0
  112. package/lib/index.js.map +1 -0
  113. package/lib/node/adapters/config/browser.d.ts +12 -0
  114. package/lib/node/adapters/config/browser.d.ts.map +1 -0
  115. package/lib/node/adapters/config/node.d.ts +13 -0
  116. package/lib/node/adapters/config/node.d.ts.map +1 -0
  117. package/lib/node/adapters/http/browser.d.ts +12 -0
  118. package/lib/node/adapters/http/browser.d.ts.map +1 -0
  119. package/lib/node/adapters/http/node.d.ts +12 -0
  120. package/lib/node/adapters/http/node.d.ts.map +1 -0
  121. package/lib/node/browser/factory.d.ts +19 -0
  122. package/lib/node/browser/factory.d.ts.map +1 -0
  123. package/lib/node/browser/index.d.ts +9 -0
  124. package/lib/node/browser/index.d.ts.map +1 -0
  125. package/lib/node/core/client.d.ts +29 -0
  126. package/lib/node/core/client.d.ts.map +1 -0
  127. package/lib/node/core/deployment.d.ts +20 -0
  128. package/lib/node/core/deployment.d.ts.map +1 -0
  129. package/lib/node/core/errors.d.ts +15 -0
  130. package/lib/node/core/errors.d.ts.map +1 -0
  131. package/lib/node/core/index.d.ts +10 -0
  132. package/lib/node/core/index.d.ts.map +1 -0
  133. package/lib/node/core/interfaces.d.ts +24 -0
  134. package/lib/node/core/interfaces.d.ts.map +1 -0
  135. package/lib/node/core/logger.d.ts +8 -0
  136. package/lib/node/core/logger.d.ts.map +1 -0
  137. package/lib/node/core/polling.d.ts +23 -0
  138. package/lib/node/core/polling.d.ts.map +1 -0
  139. package/lib/node/core/session.d.ts +31 -0
  140. package/lib/node/core/session.d.ts.map +1 -0
  141. package/lib/node/core/types.d.ts +175 -0
  142. package/lib/node/core/types.d.ts.map +1 -0
  143. package/lib/node/core/utils.d.ts +27 -0
  144. package/lib/node/core/utils.d.ts.map +1 -0
  145. package/lib/node/factory.d.ts +9 -0
  146. package/lib/node/factory.d.ts.map +1 -0
  147. package/lib/node/factory.js +34 -0
  148. package/lib/node/factory.js.map +1 -0
  149. package/lib/node/index.d.ts +8 -0
  150. package/lib/node/index.d.ts.map +1 -0
  151. package/lib/node/index.js +984 -0
  152. package/lib/node/index.js.map +1 -0
  153. package/lib/node/node/factory.d.ts +9 -0
  154. package/lib/node/node/factory.d.ts.map +1 -0
  155. package/lib/node/node/index.d.ts +8 -0
  156. package/lib/node/node/index.d.ts.map +1 -0
  157. package/package.json +98 -0
@@ -0,0 +1,973 @@
1
+ /* eslint-disable import/prefer-default-export */
2
+ // Deployment step constants
3
+ const STEPS = {
4
+ PAYMENT_PENDING: 'payment_pending',
5
+ PAYMENT_COMPLETED: 'payment_completed',
6
+ INSTALLATION_STARTING: 'installation_starting',
7
+ INSTALLATION_COMPLETED: 'installation_completed',
8
+ SERVICE_STARTING: 'service_starting',
9
+ SERVICE_READY: 'service_ready',
10
+ ACCESS_PREPARING: 'access_preparing',
11
+ ACCESS_READY: 'access_ready',
12
+ };
13
+ const DEFAULT_CONFIG = {
14
+ timeout: 300000, // 5 minutes
15
+ polling: {
16
+ interval: 3000, // 3 seconds
17
+ maxAttempts: 100,
18
+ backoffStrategy: 'linear',
19
+ },
20
+ };
21
+ const API_ENDPOINTS = {
22
+ createCheckout: '/api/checkout-sessions/start',
23
+ paymentPage: '/checkout/pay/{id}',
24
+ orderStatus: '/api/vendors/order/{id}/status',
25
+ orderDetail: '/api/vendors/order/{id}/detail',
26
+ vendors: '/api/sessions/{id}/vendors',
27
+ };
28
+ const DEPLOYMENT_STEPS = {
29
+ CREATING_SESSION: 'creating_session',
30
+ WAITING_PAYMENT: 'waiting_payment',
31
+ INSTALLING: 'installing',
32
+ STARTING_SERVICE: 'starting_service',
33
+ GETTING_URLS: 'getting_urls',
34
+ COMPLETED: 'completed',
35
+ };
36
+ const ERROR_CODES = {
37
+ NETWORK_ERROR: 'NETWORK_ERROR',
38
+ TIMEOUT_ERROR: 'TIMEOUT_ERROR',
39
+ PAYMENT_ERROR: 'PAYMENT_ERROR',
40
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
41
+ SESSION_ERROR: 'SESSION_ERROR',
42
+ DEPLOYMENT_ERROR: 'DEPLOYMENT_ERROR',
43
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
44
+ };
45
+
46
+ /* eslint-disable import/prefer-default-export */
47
+ function createDeploymentError(message, code, options) {
48
+ const error = new Error(message);
49
+ error.code = code;
50
+ error.step = options?.step;
51
+ error.sessionId = options?.sessionId;
52
+ error.paymentUrl = options?.paymentUrl;
53
+ error.linkId = options?.linkId;
54
+ error.timestamp = new Date().toISOString();
55
+ error.details = options?.details;
56
+ error.recovery = options?.recovery;
57
+ return error;
58
+ }
59
+ function isDeploymentError(error) {
60
+ return error && typeof error === 'object' && 'code' in error;
61
+ }
62
+
63
+ /* eslint-disable import/prefer-default-export */
64
+ const httpUtils = {
65
+ async request(url, options = {}) {
66
+ const response = await fetch(url, {
67
+ ...options,
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ ...options.headers,
71
+ },
72
+ });
73
+ if (!response.ok) {
74
+ throw createDeploymentError(`HTTP ${response.status}: ${response.statusText}`, 'NETWORK_ERROR', {
75
+ details: { status: response.status, statusText: response.statusText },
76
+ recovery: {
77
+ canRetry: response.status >= 500, // Server errors can be retried
78
+ suggestions: response.status === 404
79
+ ? ['Check if the URL is correct', 'Verify the service is running']
80
+ : ['Check your network connection', 'Try again later'],
81
+ },
82
+ });
83
+ }
84
+ return response.json();
85
+ },
86
+ get(url, headers) {
87
+ return this.request(url, { method: 'GET', headers });
88
+ },
89
+ post(url, data, headers) {
90
+ return this.request(url, {
91
+ method: 'POST',
92
+ body: data ? JSON.stringify(data) : undefined,
93
+ headers,
94
+ });
95
+ },
96
+ put(url, data, headers) {
97
+ return this.request(url, {
98
+ method: 'PUT',
99
+ body: data ? JSON.stringify(data) : undefined,
100
+ headers,
101
+ });
102
+ },
103
+ delete(url, headers) {
104
+ return this.request(url, { method: 'DELETE', headers });
105
+ },
106
+ };
107
+ const urlUtils = {
108
+ join(...parts) {
109
+ return parts
110
+ .map((part, index) => {
111
+ if (index === 0) {
112
+ return part.replace(/\/+$/, '');
113
+ }
114
+ return part.replace(/^\/+/, '').replace(/\/+$/, '');
115
+ })
116
+ .filter(Boolean)
117
+ .join('/');
118
+ },
119
+ addParams(url, params) {
120
+ const urlObj = new URL(url);
121
+ Object.entries(params).forEach(([key, value]) => {
122
+ if (value !== undefined && value !== null) {
123
+ urlObj.searchParams.set(key, String(value));
124
+ }
125
+ });
126
+ return urlObj.toString();
127
+ },
128
+ replaceParams(url, params) {
129
+ let result = url;
130
+ Object.entries(params).forEach(([key, value]) => {
131
+ result = result.replace(`:${key}`, value);
132
+ });
133
+ return result;
134
+ },
135
+ isValidUrl(url) {
136
+ try {
137
+ // eslint-disable-next-line no-new
138
+ new URL(url);
139
+ return true;
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ },
145
+ };
146
+ const timeUtils = {
147
+ formatDuration(ms) {
148
+ const seconds = Math.floor(ms / 1000);
149
+ const minutes = Math.floor(seconds / 60);
150
+ const hours = Math.floor(minutes / 60);
151
+ if (hours > 0) {
152
+ return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
153
+ }
154
+ if (minutes > 0) {
155
+ return `${minutes}m ${seconds % 60}s`;
156
+ }
157
+ return `${seconds}s`;
158
+ },
159
+ sleep(ms) {
160
+ return new Promise((resolve) => {
161
+ setTimeout(resolve, ms);
162
+ });
163
+ },
164
+ timeout(promise, ms) {
165
+ return Promise.race([
166
+ promise,
167
+ new Promise((_, reject) => {
168
+ setTimeout(() => reject(createDeploymentError('Operation timed out', 'TIMEOUT_ERROR', {
169
+ recovery: {
170
+ canRetry: true,
171
+ suggestions: ['Try again with a longer timeout', 'Check your network connection'],
172
+ },
173
+ })), ms);
174
+ }),
175
+ ]);
176
+ },
177
+ now() {
178
+ return new Date().toISOString();
179
+ },
180
+ };
181
+ const objectUtils = {
182
+ deepMerge(target, ...sources) {
183
+ if (!sources.length)
184
+ return target;
185
+ const source = sources.shift();
186
+ if (this.isObject(target) && this.isObject(source)) {
187
+ Object.keys(source).forEach((key) => {
188
+ if (this.isObject(source[key])) {
189
+ if (!target[key])
190
+ Object.assign(target, { [key]: {} });
191
+ this.deepMerge(target[key], source[key]);
192
+ }
193
+ else {
194
+ Object.assign(target, { [key]: source[key] });
195
+ }
196
+ });
197
+ }
198
+ return this.deepMerge(target, ...sources);
199
+ },
200
+ isObject(item) {
201
+ return item && typeof item === 'object' && !Array.isArray(item);
202
+ },
203
+ pick(obj, keys) {
204
+ const result = {};
205
+ keys.forEach((key) => {
206
+ if (key in obj) {
207
+ result[key] = obj[key];
208
+ }
209
+ });
210
+ return result;
211
+ },
212
+ omit(obj, keys) {
213
+ const result = { ...obj };
214
+ keys.forEach((key) => {
215
+ delete result[key];
216
+ });
217
+ return result;
218
+ },
219
+ deepClone(obj) {
220
+ if (obj === null || typeof obj !== 'object') {
221
+ return obj;
222
+ }
223
+ if (obj instanceof Date) {
224
+ return new Date(obj.getTime());
225
+ }
226
+ if (obj instanceof Array) {
227
+ return obj.map((item) => this.deepClone(item));
228
+ }
229
+ if (typeof obj === 'object') {
230
+ const cloned = {};
231
+ Object.keys(obj).forEach((key) => {
232
+ cloned[key] = this.deepClone(obj[key]);
233
+ });
234
+ return cloned;
235
+ }
236
+ return obj;
237
+ },
238
+ };
239
+
240
+ /* eslint-disable import/prefer-default-export */
241
+ // Internal debug logger - only logs when BROKER_CLIENT_DEBUG=true
242
+ const isDebugEnabled = (() => {
243
+ try {
244
+ return typeof process !== 'undefined' && process.env?.BROKER_CLIENT_DEBUG === 'true';
245
+ }
246
+ catch {
247
+ // In browser environments, check localStorage or global variables
248
+ return typeof window !== 'undefined' && window.BROKER_CLIENT_DEBUG === 'true';
249
+ }
250
+ })();
251
+ const logger = {
252
+ debug: (message, data) => {
253
+ if (isDebugEnabled) {
254
+ // eslint-disable-next-line no-console
255
+ console.log(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
256
+ }
257
+ },
258
+ info: (message, data) => {
259
+ if (isDebugEnabled) {
260
+ // eslint-disable-next-line no-console
261
+ console.log(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
262
+ }
263
+ },
264
+ warn: (message, data) => {
265
+ if (isDebugEnabled) {
266
+ console.warn(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
267
+ }
268
+ },
269
+ error: (message, data) => {
270
+ if (isDebugEnabled) {
271
+ console.error(`[BROKER-CLIENT-DEBUG] ${message}`, data ? JSON.stringify(data) : '');
272
+ }
273
+ },
274
+ };
275
+
276
+ /* eslint-disable import/prefer-default-export */
277
+ class SessionManager {
278
+ constructor(config, deps) {
279
+ this.config = config;
280
+ this.deps = deps;
281
+ }
282
+ async getConfigInfo() {
283
+ if (this.cachedConfig) {
284
+ return this.cachedConfig;
285
+ }
286
+ const blockletInfo = await this.deps.configProvider.getBlockletInfo();
287
+ const paymentLinkKey = this.config.paymentLinkKey || 'PAYMENT_LINK_ID';
288
+ const linkId = blockletInfo[paymentLinkKey];
289
+ if (!linkId) {
290
+ throw createDeploymentError(`Payment link ID not found using key '${paymentLinkKey}' in blocklet configuration`, 'VALIDATION_ERROR', {
291
+ step: 'creating_session',
292
+ recovery: {
293
+ canRetry: false,
294
+ suggestions: [
295
+ `Make sure ${paymentLinkKey} is set in the environment`,
296
+ 'Check if the Payment Kit is properly configured',
297
+ 'Verify the paymentLinkKey configuration is correct',
298
+ ],
299
+ },
300
+ });
301
+ }
302
+ const paymentKitDid = 'z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk';
303
+ let mountPoint = '/';
304
+ if (blockletInfo.componentMountPoints && Array.isArray(blockletInfo.componentMountPoints)) {
305
+ const paymentKitComponent = blockletInfo.componentMountPoints.find((component) => component.did === paymentKitDid || component.appId === paymentKitDid);
306
+ if (paymentKitComponent?.mountPoint) {
307
+ mountPoint = paymentKitComponent.mountPoint;
308
+ }
309
+ }
310
+ this.cachedConfig = { linkId, mountPoint, paymentKitDid };
311
+ logger.info('Configuration processed', this.cachedConfig);
312
+ return this.cachedConfig;
313
+ }
314
+ /**
315
+ * Get linkId
316
+ */
317
+ async getLinkId() {
318
+ const { linkId } = await this.getConfigInfo();
319
+ return linkId;
320
+ }
321
+ /**
322
+ * Get mountPoint
323
+ */
324
+ async getMountPoint() {
325
+ const { mountPoint } = await this.getConfigInfo();
326
+ return mountPoint;
327
+ }
328
+ /**
329
+ * Get paymentKitDid
330
+ */
331
+ async getPaymentKitDid() {
332
+ const { paymentKitDid } = await this.getConfigInfo();
333
+ return paymentKitDid;
334
+ }
335
+ async checkCacheCheckoutId(checkoutId) {
336
+ try {
337
+ if (!checkoutId) {
338
+ return '';
339
+ }
340
+ // API_ENDPOINTS already includes /api prefix
341
+ const orderStatusUrl = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', checkoutId));
342
+ const response = await this.deps.httpClient.get(orderStatusUrl);
343
+ if (response.error) {
344
+ throw new Error(response.error);
345
+ }
346
+ // Check payment status
347
+ const isPaid = response.payment_status === 'paid';
348
+ return isPaid ? checkoutId : '';
349
+ }
350
+ catch {
351
+ return '';
352
+ }
353
+ }
354
+ async createSession(options = {}) {
355
+ const { linkId, mountPoint, paymentKitDid } = await this.getConfigInfo();
356
+ logger.info('Creating payment session', { linkId, mountPoint, paymentKitDid });
357
+ try {
358
+ // Build URL - API_ENDPOINTS already includes /api prefix
359
+ const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.createCheckout, linkId);
360
+ const { needShortUrl = true, page_info: pageInfo } = options;
361
+ const payload = {
362
+ needShortUrl,
363
+ metadata: {
364
+ page_info: {
365
+ has_vendor: true,
366
+ ...pageInfo,
367
+ },
368
+ },
369
+ };
370
+ const response = await timeUtils.timeout(this.deps.httpClient.post(url, payload), this.config.timeout || 30000);
371
+ const session = {
372
+ id: response.checkoutSession.id,
373
+ paymentUrl: response.paymentUrl,
374
+ status: 'pending',
375
+ linkId,
376
+ vendorDid: paymentKitDid,
377
+ createdAt: timeUtils.now(),
378
+ updatedAt: timeUtils.now(),
379
+ expiresAt: new Date(Date.now() + (this.config.timeout || 300000)).toISOString(),
380
+ currency: 'USD',
381
+ };
382
+ logger.info('Session created successfully', { sessionId: session.id });
383
+ return session;
384
+ }
385
+ catch (error) {
386
+ logger.error('Failed to create session', { error: error.message, linkId });
387
+ throw createDeploymentError(`Failed to create session: ${error.message}`, 'SESSION_ERROR', {
388
+ step: 'creating_session',
389
+ linkId,
390
+ details: { originalError: error },
391
+ recovery: {
392
+ canRetry: true,
393
+ suggestions: ['Check your network connection', 'Verify the Payment Kit service is available'],
394
+ },
395
+ });
396
+ }
397
+ }
398
+ async getSessionStatus(sessionId) {
399
+ logger.debug('Getting session status', { sessionId });
400
+ try {
401
+ // API_ENDPOINTS already includes /api prefix
402
+ const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', sessionId));
403
+ const response = await timeUtils.timeout(this.deps.httpClient.get(url), this.config.timeout || 30000);
404
+ logger.debug('Session status retrieved', { sessionId, status: response.payment_status });
405
+ return response;
406
+ }
407
+ catch (error) {
408
+ logger.error('Failed to get session status', { error: error.message, sessionId });
409
+ throw createDeploymentError(`Failed to get session status: ${error.message}`, 'SESSION_ERROR', {
410
+ sessionId,
411
+ recovery: {
412
+ canRetry: true,
413
+ suggestions: ['Check your network connection', 'Verify the session ID is correct'],
414
+ },
415
+ });
416
+ }
417
+ }
418
+ waitForPayment(sessionId) {
419
+ logger.info('Waiting for payment completion', { sessionId });
420
+ return this.deps.pollingManager.poll({
421
+ checkCondition: async () => {
422
+ const status = await this.getSessionStatus(sessionId);
423
+ if (status.payment_status === 'paid') {
424
+ logger.info('Payment completed', { sessionId });
425
+ return status.vendors || true;
426
+ }
427
+ if (status.payment_status === 'expired' || status.payment_status === 'cancelled') {
428
+ throw createDeploymentError(`Payment ${status.payment_status}`, 'SESSION_ERROR', {
429
+ sessionId,
430
+ recovery: {
431
+ canRetry: status.payment_status === 'expired',
432
+ suggestions: status.payment_status === 'expired'
433
+ ? ['Create a new payment session', 'Try the deployment again']
434
+ : ['Payment was cancelled', 'Start a new deployment if needed'],
435
+ },
436
+ });
437
+ }
438
+ return false;
439
+ },
440
+ stepName: 'waiting for payment',
441
+ maxAttempts: this.config.polling?.maxAttempts || 100,
442
+ interval: this.config.polling?.interval || 3000,
443
+ });
444
+ }
445
+ async getOrderDetails(sessionId) {
446
+ logger.debug('Getting order details', { sessionId });
447
+ try {
448
+ // API_ENDPOINTS already includes /api prefix
449
+ const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderDetail.replace('{id}', sessionId));
450
+ const response = await timeUtils.timeout(this.deps.httpClient.get(url), this.config.timeout || 30000);
451
+ logger.debug('Order details retrieved', { sessionId });
452
+ return response;
453
+ }
454
+ catch (error) {
455
+ logger.error('Failed to get order details', { error: error.message, sessionId });
456
+ throw createDeploymentError(`Failed to get order details: ${error.message}`, 'SESSION_ERROR', {
457
+ sessionId,
458
+ recovery: {
459
+ canRetry: true,
460
+ suggestions: ['Check your network connection', 'Verify the session is still active'],
461
+ },
462
+ });
463
+ }
464
+ }
465
+ }
466
+
467
+ /* eslint-disable import/prefer-default-export */
468
+ /**
469
+ * Unified DeploymentManager class
470
+ * Uses dependency injection to support different environment implementations
471
+ */
472
+ class DeploymentManager {
473
+ constructor(config, deps) {
474
+ this.config = config;
475
+ this.deps = deps;
476
+ this.installationStarted = false;
477
+ this.serviceStartCallbackCalled = false;
478
+ this.currentDeployment = null;
479
+ }
480
+ async startDeployment(sessionId, options) {
481
+ this.currentDeployment = {
482
+ sessionId,
483
+ startTime: timeUtils.now(),
484
+ };
485
+ this.installationStarted = false;
486
+ this.serviceStartCallbackCalled = false;
487
+ logger.info('Starting deployment', { sessionId });
488
+ try {
489
+ await this.waitForPaymentCompletion(sessionId, options);
490
+ const vendors = await this.waitForInstallation(sessionId, options);
491
+ await this.waitForServiceStartup(vendors, options);
492
+ const result = await this.getFinalUrls(sessionId, vendors, options);
493
+ this.currentDeployment.result = result;
494
+ logger.info('Deployment completed successfully', { sessionId, duration: result.duration });
495
+ return result;
496
+ }
497
+ catch (error) {
498
+ logger.error('Deployment failed', { sessionId, error: error.message });
499
+ this.currentDeployment.error = error;
500
+ options.onError?.(error, 'getting_urls'); // Call onError
501
+ throw error;
502
+ }
503
+ }
504
+ async waitForPaymentCompletion(sessionId, options) {
505
+ logger.info('Waiting for payment to be marked as paid', { sessionId });
506
+ await this.deps.pollingManager.poll({
507
+ checkCondition: async () => {
508
+ // API_ENDPOINTS already includes /api prefix
509
+ const orderStatusUrl = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', sessionId));
510
+ const response = await this.deps.httpClient.get(orderStatusUrl);
511
+ if (response.session_status === 'complete') {
512
+ const vendors = response.vendors || [];
513
+ await options.hooks?.[STEPS.PAYMENT_COMPLETED]?.({
514
+ sessionId,
515
+ vendors,
516
+ });
517
+ return true;
518
+ }
519
+ return false;
520
+ },
521
+ stepName: 'waiting_payment',
522
+ });
523
+ }
524
+ waitForInstallation(sessionId, options) {
525
+ logger.info('Waiting for installation to start', { sessionId });
526
+ return this.deps.pollingManager.poll({
527
+ checkCondition: async () => {
528
+ // API_ENDPOINTS already includes /api prefix
529
+ const url = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderStatus.replace('{id}', sessionId));
530
+ const response = await this.deps.httpClient.get(url);
531
+ const vendors = response.vendors || [];
532
+ if (vendors.length === 0) {
533
+ return false;
534
+ }
535
+ if (vendors.length > 0 && !this.installationStarted) {
536
+ this.installationStarted = true;
537
+ await options.hooks?.[STEPS.INSTALLATION_STARTING]?.({
538
+ vendors,
539
+ });
540
+ }
541
+ // Check if all vendors meet conditions: progress >= 80 and appUrl exists
542
+ const isInstalled = vendors.every((vendor) => vendor.progress >= 80 && vendor.appUrl);
543
+ if (isInstalled) {
544
+ const avgProgress = vendors.reduce((sum, v) => sum + v.progress, 0) / vendors.length;
545
+ await options.hooks?.[STEPS.INSTALLATION_COMPLETED]?.({
546
+ vendors,
547
+ progress: Math.round(avgProgress),
548
+ });
549
+ return vendors;
550
+ }
551
+ return false;
552
+ },
553
+ stepName: 'installation_started',
554
+ });
555
+ }
556
+ async waitForServiceStartup(vendors, options) {
557
+ logger.info('Services starting', { vendorCount: vendors.length });
558
+ // Since we don't need to check service status, we can directly call the callbacks
559
+ await options.hooks?.[STEPS.SERVICE_STARTING]?.({
560
+ vendors,
561
+ });
562
+ // Wait a brief moment for services to initialize
563
+ await new Promise((resolve) => {
564
+ setTimeout(resolve, 2000);
565
+ });
566
+ logger.info('Services started successfully');
567
+ await options.hooks?.[STEPS.SERVICE_READY]?.({
568
+ vendors,
569
+ });
570
+ }
571
+ async getFinalUrls(sessionId, runningVendors, options) {
572
+ try {
573
+ await options.hooks?.[STEPS.ACCESS_PREPARING]?.({
574
+ sessionId,
575
+ });
576
+ // API_ENDPOINTS already includes /api prefix
577
+ const orderDetailUrl = urlUtils.join(this.config.baseUrl, API_ENDPOINTS.orderDetail.replace('{id}', sessionId));
578
+ const data = await this.deps.httpClient.get(orderDetailUrl);
579
+ if (data.vendors.length === 0) {
580
+ throw createDeploymentError('No vendors found in order details', 'DEPLOYMENT_ERROR', {
581
+ step: 'getting_urls',
582
+ sessionId,
583
+ recovery: {
584
+ canRetry: true,
585
+ suggestions: ['Wait a moment and try again', 'Check if the deployment completed successfully'],
586
+ },
587
+ });
588
+ }
589
+ // Wait 3 seconds like in the original code
590
+ await timeUtils.sleep(3000);
591
+ // Return the URLs from the API response, fallback to running vendor data
592
+ const apiVendor = data.vendors[0];
593
+ const primaryVendor = runningVendors[0];
594
+ const endTime = timeUtils.now();
595
+ const duration = this.currentDeployment.startTime
596
+ ? new Date(endTime).getTime() - new Date(this.currentDeployment.startTime).getTime()
597
+ : 0;
598
+ const result = {
599
+ success: true,
600
+ sessionId,
601
+ appUrl: apiVendor?.appUrl || primaryVendor?.appUrl,
602
+ dashboardUrl: apiVendor?.dashboardUrl || primaryVendor?.dashboardUrl,
603
+ homeUrl: apiVendor?.homeUrl || primaryVendor?.homeUrl,
604
+ token: apiVendor?.token || primaryVendor?.token,
605
+ subscriptionUrl: data.subscriptionUrl,
606
+ vendors: data.vendors || runningVendors,
607
+ duration,
608
+ startTime: this.currentDeployment.startTime || timeUtils.now(),
609
+ endTime,
610
+ metadata: data.metadata,
611
+ data: data.data,
612
+ };
613
+ await options.hooks?.[STEPS.ACCESS_READY]?.({
614
+ sessionId,
615
+ appUrl: result.appUrl,
616
+ dashboardUrl: result.dashboardUrl,
617
+ homeUrl: result.homeUrl,
618
+ subscriptionUrl: result.subscriptionUrl,
619
+ });
620
+ return result;
621
+ }
622
+ catch (error) {
623
+ logger.error('Failed to get final URLs', { sessionId, error: error.message });
624
+ // If getting details fails, use the appUrl of running vendor
625
+ return {
626
+ success: true,
627
+ sessionId,
628
+ appUrl: runningVendors[0]?.appUrl || null,
629
+ dashboardUrl: runningVendors[0]?.dashboardUrl || null,
630
+ homeUrl: runningVendors[0]?.homeUrl || null,
631
+ token: runningVendors[0]?.token || null,
632
+ duration: this.currentDeployment.startTime
633
+ ? new Date().getTime() - new Date(this.currentDeployment.startTime).getTime()
634
+ : 0,
635
+ };
636
+ }
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Unified BrokerClient class
642
+ * Uses dependency injection to support different environment implementations
643
+ */
644
+ class BrokerClient {
645
+ constructor(config, dependencies) {
646
+ this.isDeploying = false;
647
+ this.currentDeployment = null;
648
+ this.validateConfig(config);
649
+ this.config = objectUtils.deepMerge({}, DEFAULT_CONFIG, config);
650
+ this.sessionManager = new SessionManager(this.config, dependencies);
651
+ this.deploymentManager = new DeploymentManager(this.config, dependencies);
652
+ }
653
+ async deploy(options) {
654
+ if (this.isDeploying) {
655
+ throw createDeploymentError('Deployment already in progress', 'VALIDATION_ERROR');
656
+ }
657
+ this.isDeploying = true;
658
+ this.currentDeployment = {
659
+ sessionId: '', // Will be set after session creation
660
+ startTime: timeUtils.now(),
661
+ };
662
+ try {
663
+ let sessionId;
664
+ let paymentUrl = options.cachedPaymentUrl;
665
+ // Check cached checkout session
666
+ if (options.cachedCheckoutId) {
667
+ logger.info('Checking cached checkout session', { cachedCheckoutId: options.cachedCheckoutId });
668
+ const cachedCheckoutId = await this.sessionManager.checkCacheCheckoutId(options.cachedCheckoutId);
669
+ if (cachedCheckoutId) {
670
+ sessionId = cachedCheckoutId;
671
+ logger.info('Using cached session', { sessionId });
672
+ // Build payment URL if not cached
673
+ if (!paymentUrl) {
674
+ const mountPoint = await this.sessionManager.getMountPoint();
675
+ const basePath = `${this.config.baseUrl}${mountPoint}`;
676
+ paymentUrl = `${basePath}/checkout/pay/${sessionId}`;
677
+ }
678
+ await options.hooks?.[STEPS.PAYMENT_PENDING]?.({
679
+ sessionId,
680
+ paymentUrl,
681
+ isResuming: true,
682
+ });
683
+ }
684
+ }
685
+ // Create new session if no valid cached session
686
+ if (!sessionId) {
687
+ logger.info('Creating new checkout session');
688
+ const linkId = await this.sessionManager.getLinkId();
689
+ const session = await this.sessionManager.createSession(options);
690
+ sessionId = session.id;
691
+ paymentUrl = session.paymentUrl;
692
+ await options.hooks?.[STEPS.PAYMENT_PENDING]?.({
693
+ sessionId: session.id,
694
+ paymentUrl: session.paymentUrl,
695
+ linkId,
696
+ isResuming: false,
697
+ });
698
+ }
699
+ this.currentDeployment.sessionId = sessionId;
700
+ const result = await this.deploymentManager.startDeployment(sessionId, options);
701
+ return result;
702
+ }
703
+ catch (error) {
704
+ this.isDeploying = false;
705
+ if (this.currentDeployment) {
706
+ if (isDeploymentError(error)) {
707
+ this.currentDeployment.error = error;
708
+ }
709
+ else {
710
+ this.currentDeployment.error = createDeploymentError(error.message, 'UNKNOWN_ERROR', {
711
+ sessionId: this.currentDeployment.sessionId,
712
+ details: { originalError: error },
713
+ recovery: {
714
+ canRetry: true,
715
+ suggestions: ['Check your network connection', 'Verify the baseUrl is correct'],
716
+ },
717
+ });
718
+ }
719
+ }
720
+ if (options.onError) {
721
+ const deploymentError = isDeploymentError(error) ? error : this.currentDeployment?.error;
722
+ if (deploymentError) {
723
+ options.onError(deploymentError, deploymentError.step);
724
+ }
725
+ }
726
+ throw error;
727
+ }
728
+ }
729
+ createSession(options = {}) {
730
+ return this.sessionManager.createSession(options);
731
+ }
732
+ getSessionStatus(sessionId) {
733
+ return this.sessionManager.getSessionStatus(sessionId);
734
+ }
735
+ cancelDeployment() {
736
+ if (this.isDeploying) {
737
+ logger.info('Cancelling deployment');
738
+ this.isDeploying = false;
739
+ // Note: Actual cancellation would need to be implemented in deployment manager
740
+ }
741
+ }
742
+ getDeploymentStatus() {
743
+ const result = this.currentDeployment?.result;
744
+ const error = this.currentDeployment?.error;
745
+ const sessionId = this.currentDeployment?.sessionId;
746
+ const startTime = this.currentDeployment?.startTime;
747
+ return {
748
+ isDeploying: this.isDeploying,
749
+ result,
750
+ error,
751
+ sessionId,
752
+ duration: startTime ? new Date().getTime() - new Date(startTime).getTime() : 0,
753
+ };
754
+ }
755
+ updateConfig(newConfig) {
756
+ this.config = objectUtils.deepMerge(this.config, newConfig);
757
+ // Note: In a real implementation, we might need to recreate the dependencies
758
+ // For now, we assume the dependencies remain the same
759
+ }
760
+ getConfig() {
761
+ return this.config;
762
+ }
763
+ validateConfig(config) {
764
+ if (!config.baseUrl) {
765
+ throw createDeploymentError('baseUrl is required in configuration', 'VALIDATION_ERROR');
766
+ }
767
+ try {
768
+ // eslint-disable-next-line no-new
769
+ new URL(config.baseUrl);
770
+ }
771
+ catch {
772
+ throw createDeploymentError('Invalid baseUrl format', 'VALIDATION_ERROR');
773
+ }
774
+ }
775
+ }
776
+
777
+ /* eslint-disable import/prefer-default-export */
778
+ class PollingManager {
779
+ constructor(options = {}) {
780
+ this.baseInterval = 1000;
781
+ this.maxAttempts = 60;
782
+ this.backoffStrategy = 'linear';
783
+ this.baseInterval = options.baseInterval || 1000;
784
+ this.maxAttempts = options.maxAttempts || 60;
785
+ this.backoffStrategy = options.backoffStrategy || 'linear';
786
+ }
787
+ async poll(options) {
788
+ const { checkCondition, stepName = 'polling', interval = this.baseInterval, maxAttempts = this.maxAttempts, backoffStrategy = this.backoffStrategy, onProgress, } = options;
789
+ for (let attempts = 1; attempts <= maxAttempts; attempts++) {
790
+ try {
791
+ logger.debug(`${stepName} - Attempt ${attempts}/${maxAttempts}`);
792
+ // eslint-disable-next-line no-await-in-loop
793
+ const result = await checkCondition();
794
+ if (result !== null && result !== false && result !== undefined) {
795
+ logger.debug(`${stepName} - Success on attempt ${attempts}`);
796
+ return result;
797
+ }
798
+ onProgress?.(attempts, maxAttempts);
799
+ if (attempts < maxAttempts) {
800
+ const waitTime = this.calculateWaitTime(attempts, interval, backoffStrategy);
801
+ logger.debug(`${stepName} - Waiting ${waitTime}ms before next attempt`);
802
+ // eslint-disable-next-line no-await-in-loop
803
+ await timeUtils.sleep(waitTime);
804
+ }
805
+ }
806
+ catch (error) {
807
+ logger.error(`${stepName} - Error on attempt ${attempts}:`, error.message);
808
+ if (attempts === maxAttempts) {
809
+ throw createDeploymentError(`${stepName} failed after ${maxAttempts} attempts: ${error.message}`, 'TIMEOUT_ERROR');
810
+ }
811
+ const waitTime = this.calculateWaitTime(attempts, interval, backoffStrategy);
812
+ // eslint-disable-next-line no-await-in-loop
813
+ await timeUtils.sleep(waitTime);
814
+ }
815
+ }
816
+ throw createDeploymentError(`${stepName} timed out after ${maxAttempts} attempts`, 'TIMEOUT_ERROR');
817
+ }
818
+ calculateWaitTime(attempt, baseInterval, strategy) {
819
+ switch (strategy) {
820
+ case 'linear':
821
+ return baseInterval;
822
+ case 'exponential':
823
+ return Math.min(baseInterval * 2 ** (attempt - 1), 30000);
824
+ default:
825
+ return baseInterval;
826
+ }
827
+ }
828
+ pollWithTimeout(options, timeoutMs) {
829
+ return Promise.race([
830
+ this.poll(options),
831
+ new Promise((_, reject) => {
832
+ setTimeout(() => reject(createDeploymentError('Polling timed out', 'TIMEOUT_ERROR')), timeoutMs);
833
+ }),
834
+ ]);
835
+ }
836
+ async pollBatch(items, checkCondition, options = {}) {
837
+ const completed = [];
838
+ const remaining = [...items];
839
+ while (remaining.length > 0) {
840
+ const batchPromises = remaining.map(async (item) => {
841
+ const isComplete = await checkCondition(item);
842
+ return { item, isComplete };
843
+ });
844
+ // eslint-disable-next-line no-await-in-loop
845
+ const results = await Promise.all(batchPromises);
846
+ results.forEach(({ item, isComplete }) => {
847
+ if (isComplete) {
848
+ completed.push(item);
849
+ const index = remaining.indexOf(item);
850
+ if (index > -1) {
851
+ remaining.splice(index, 1);
852
+ }
853
+ }
854
+ });
855
+ if (remaining.length > 0) {
856
+ // eslint-disable-next-line no-await-in-loop
857
+ await timeUtils.sleep(options.interval || this.baseInterval);
858
+ }
859
+ }
860
+ return completed;
861
+ }
862
+ }
863
+
864
+ /* eslint-disable import/prefer-default-export */
865
+ /**
866
+ * Browser HTTP client implementation
867
+ * Uses the passed api object (typically an axios instance)
868
+ */
869
+ class BrowserHttpClient {
870
+ constructor(api) {
871
+ if (api) {
872
+ this.api = api;
873
+ }
874
+ else {
875
+ // Create simple fetch-compatible interface as fallback
876
+ this.api = {
877
+ get: async (url) => {
878
+ const headers = { 'Content-Type': 'application/json' };
879
+ const response = await fetch(url, { method: 'GET', headers });
880
+ if (!response.ok) {
881
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
882
+ }
883
+ return { data: await response.json() };
884
+ },
885
+ post: async (url, data) => {
886
+ const headers = { 'Content-Type': 'application/json' };
887
+ const response = await fetch(url, {
888
+ method: 'POST',
889
+ headers,
890
+ body: data ? JSON.stringify(data) : undefined,
891
+ });
892
+ if (!response.ok) {
893
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
894
+ }
895
+ return { data: await response.json() };
896
+ },
897
+ };
898
+ }
899
+ }
900
+ async get(url) {
901
+ const response = await this.api.get(url);
902
+ return response.data;
903
+ }
904
+ async post(url, data) {
905
+ const response = await this.api.post(url, data);
906
+ return response.data;
907
+ }
908
+ }
909
+
910
+ /* eslint-disable import/prefer-default-export */
911
+ /**
912
+ * Browser configuration provider implementation
913
+ * Gets raw blocklet information from window.blocklet
914
+ */
915
+ class BrowserConfigProvider {
916
+ constructor(config) {
917
+ this.config = config;
918
+ }
919
+ getBlockletInfo() {
920
+ return Promise.resolve().then(() => {
921
+ try {
922
+ // Get blocklet info from window.blocklet object
923
+ const { blocklet } = window;
924
+ if (!blocklet) {
925
+ throw new Error('window.blocklet is not available. Make sure you are running in a blocklet environment.');
926
+ }
927
+ logger.info('Blocklet info found from window.blocklet', { hasBlocklet: true });
928
+ return blocklet;
929
+ }
930
+ catch (error) {
931
+ logger.error('Failed to get blocklet info from window.blocklet', { error: error.message });
932
+ throw createDeploymentError(`Failed to get blocklet info from window.blocklet: ${error.message}. Please ensure you are running in a blocklet environment.`, 'VALIDATION_ERROR', {
933
+ step: 'creating_session',
934
+ details: { originalError: error },
935
+ recovery: {
936
+ canRetry: false,
937
+ suggestions: [
938
+ 'Make sure you are running this in a blocklet environment',
939
+ 'Check if window.blocklet is available',
940
+ 'Verify the Payment Kit is properly configured',
941
+ ],
942
+ },
943
+ });
944
+ }
945
+ });
946
+ }
947
+ }
948
+
949
+ /* eslint-disable import/prefer-default-export */
950
+ /**
951
+ * Creates a Browser specific BrokerClient instance
952
+ * @param config - Browser specific configuration for the broker client
953
+ * @returns Configured BrokerClient instance with Browser dependencies
954
+ */
955
+ function createBrowserBrokerClient(config) {
956
+ const fullConfig = { ...config };
957
+ // Create HTTP client with optional api instance
958
+ const httpClient = new BrowserHttpClient(config.api);
959
+ // Create dependencies for Browser environment
960
+ const dependencies = {
961
+ httpClient,
962
+ configProvider: new BrowserConfigProvider(fullConfig),
963
+ pollingManager: new PollingManager({
964
+ baseInterval: fullConfig.polling?.interval || 3000,
965
+ maxAttempts: fullConfig.polling?.maxAttempts || 100,
966
+ backoffStrategy: fullConfig.polling?.backoffStrategy || 'linear',
967
+ }),
968
+ };
969
+ return new BrokerClient(fullConfig, dependencies);
970
+ }
971
+
972
+ export { API_ENDPOINTS, createBrowserBrokerClient as BrokerClient, DEFAULT_CONFIG, DEPLOYMENT_STEPS, ERROR_CODES, PollingManager, STEPS, createDeploymentError, httpUtils, isDeploymentError, logger, objectUtils, timeUtils, urlUtils };
973
+ //# sourceMappingURL=index.js.map