@youidian/sdk 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.cjs +547 -2
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +2 -1
- package/dist/client.d.ts +2 -1
- package/dist/client.js +543 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +386 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +388 -153
- package/dist/index.js.map +1 -1
- package/dist/login-D_VaJqQX.d.cts +86 -0
- package/dist/login-D_VaJqQX.d.ts +86 -0
- package/dist/login.cjs +277 -41
- package/dist/login.cjs.map +1 -1
- package/dist/login.d.cts +1 -62
- package/dist/login.d.ts +1 -62
- package/dist/login.js +275 -40
- package/dist/login.js.map +1 -1
- package/package.json +36 -24
- package/dist/hosted-modal-BZmYmXTU.d.cts +0 -20
- package/dist/hosted-modal-BZmYmXTU.d.ts +0 -20
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,8 @@ __export(src_exports, {
|
|
|
36
36
|
PaymentClient: () => PaymentClient,
|
|
37
37
|
PaymentUI: () => PaymentUI,
|
|
38
38
|
createLoginUI: () => createLoginUI,
|
|
39
|
-
createPaymentUI: () => createPaymentUI
|
|
39
|
+
createPaymentUI: () => createPaymentUI,
|
|
40
|
+
handleLoginCallbackIfPresent: () => handleLoginCallbackIfPresent
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(src_exports);
|
|
42
43
|
|
|
@@ -237,115 +238,6 @@ var HostedFrameModal = class {
|
|
|
237
238
|
}
|
|
238
239
|
};
|
|
239
240
|
|
|
240
|
-
// src/client.ts
|
|
241
|
-
var PaymentUI = class extends HostedFrameModal {
|
|
242
|
-
/**
|
|
243
|
-
* Opens the payment checkout page in an iframe modal.
|
|
244
|
-
* @param urlOrParams - The checkout page URL or payment parameters
|
|
245
|
-
* @param options - UI options
|
|
246
|
-
*/
|
|
247
|
-
openPayment(urlOrParams, options) {
|
|
248
|
-
if (typeof document === "undefined") return;
|
|
249
|
-
if (this.modal) return;
|
|
250
|
-
let checkoutUrl;
|
|
251
|
-
if (typeof urlOrParams === "string") {
|
|
252
|
-
checkoutUrl = urlOrParams;
|
|
253
|
-
} else {
|
|
254
|
-
const {
|
|
255
|
-
appId,
|
|
256
|
-
productId,
|
|
257
|
-
priceId,
|
|
258
|
-
productCode,
|
|
259
|
-
userId,
|
|
260
|
-
checkoutUrl: checkoutUrlParam,
|
|
261
|
-
baseUrl = "https://pay.imgto.link"
|
|
262
|
-
} = urlOrParams;
|
|
263
|
-
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
264
|
-
if (productCode) {
|
|
265
|
-
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
266
|
-
} else if (productId && priceId) {
|
|
267
|
-
checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
|
|
268
|
-
} else {
|
|
269
|
-
throw new Error(
|
|
270
|
-
"Either productCode or both productId and priceId are required"
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
|
|
275
|
-
this.openHostedFrame(finalUrl, {
|
|
276
|
-
allowedOrigin: options?.allowedOrigin,
|
|
277
|
-
onCloseButton: () => options?.onCancel?.(),
|
|
278
|
-
onMessage: (data, container) => {
|
|
279
|
-
switch (data.type) {
|
|
280
|
-
case "PAYMENT_SUCCESS":
|
|
281
|
-
options?.onSuccess?.(data.orderId);
|
|
282
|
-
break;
|
|
283
|
-
case "PAYMENT_CANCELLED":
|
|
284
|
-
options?.onCancel?.(data.orderId);
|
|
285
|
-
break;
|
|
286
|
-
case "PAYMENT_RESIZE":
|
|
287
|
-
if (data.height) {
|
|
288
|
-
const maxHeight = window.innerHeight * 0.9;
|
|
289
|
-
const newHeight = Math.min(data.height, maxHeight);
|
|
290
|
-
container.style.height = `${newHeight}px`;
|
|
291
|
-
}
|
|
292
|
-
break;
|
|
293
|
-
case "PAYMENT_CLOSE":
|
|
294
|
-
this.close();
|
|
295
|
-
options?.onClose?.();
|
|
296
|
-
break;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
/**
|
|
302
|
-
* Poll order status from integrator's API endpoint
|
|
303
|
-
* @param statusUrl - The integrator's API endpoint to check order status
|
|
304
|
-
* @param options - Polling options
|
|
305
|
-
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
306
|
-
*/
|
|
307
|
-
async pollOrderStatus(statusUrl, options) {
|
|
308
|
-
const interval = options?.interval || 3e3;
|
|
309
|
-
const timeout = options?.timeout || 3e5;
|
|
310
|
-
const startTime = Date.now();
|
|
311
|
-
return new Promise((resolve, reject) => {
|
|
312
|
-
const poll = async () => {
|
|
313
|
-
try {
|
|
314
|
-
const response = await fetch(statusUrl);
|
|
315
|
-
if (!response.ok) {
|
|
316
|
-
throw new Error(`Status check failed: ${response.status}`);
|
|
317
|
-
}
|
|
318
|
-
const status = await response.json();
|
|
319
|
-
options?.onStatusChange?.(status);
|
|
320
|
-
if (status.status === "PAID") {
|
|
321
|
-
resolve(status);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
325
|
-
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
if (Date.now() - startTime > timeout) {
|
|
329
|
-
reject(new Error("Polling timeout"));
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
setTimeout(poll, interval);
|
|
333
|
-
} catch (error) {
|
|
334
|
-
if (Date.now() - startTime > timeout) {
|
|
335
|
-
reject(error);
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
setTimeout(poll, interval);
|
|
339
|
-
}
|
|
340
|
-
};
|
|
341
|
-
poll();
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
|
-
function createPaymentUI() {
|
|
346
|
-
return new PaymentUI();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
241
|
// src/login.ts
|
|
350
242
|
function getOrigin(value) {
|
|
351
243
|
if (!value) return null;
|
|
@@ -355,10 +247,70 @@ function getOrigin(value) {
|
|
|
355
247
|
return null;
|
|
356
248
|
}
|
|
357
249
|
}
|
|
358
|
-
function
|
|
250
|
+
function createLoginCallbackState() {
|
|
251
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
252
|
+
return crypto.randomUUID().replace(/-/g, "");
|
|
253
|
+
}
|
|
254
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
255
|
+
}
|
|
256
|
+
function buildLoginCallbackUrl(callbackState, callbackUrl) {
|
|
257
|
+
const baseUrl = callbackUrl || window.location.href;
|
|
258
|
+
const url = new URL(baseUrl, window.location.href);
|
|
259
|
+
const returnHash = url.hash.replace(/^#/, "");
|
|
260
|
+
url.hash = "";
|
|
261
|
+
url.searchParams.set(LOGIN_CALLBACK_MARKER, "1");
|
|
262
|
+
url.searchParams.set(LOGIN_CALLBACK_STATE_PARAM, callbackState);
|
|
263
|
+
if (returnHash) {
|
|
264
|
+
url.searchParams.set(LOGIN_CALLBACK_RETURN_HASH_PARAM, returnHash);
|
|
265
|
+
} else {
|
|
266
|
+
url.searchParams.delete(LOGIN_CALLBACK_RETURN_HASH_PARAM);
|
|
267
|
+
}
|
|
268
|
+
return url.toString();
|
|
269
|
+
}
|
|
270
|
+
function buildLoginLaunchPayload(params, options, callbackState) {
|
|
271
|
+
const parentOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
|
|
272
|
+
const callbackUrl = typeof window !== "undefined" && callbackState ? buildLoginCallbackUrl(callbackState, options?.callbackUrl) : void 0;
|
|
273
|
+
return {
|
|
274
|
+
autoClose: options?.autoClose ?? true,
|
|
275
|
+
callbackUrl,
|
|
276
|
+
callbackState,
|
|
277
|
+
origin: parentOrigin,
|
|
278
|
+
preferredChannel: params.preferredChannel
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function appendLoginLaunchHash(urlValue, payload) {
|
|
282
|
+
const hashParams = new URLSearchParams();
|
|
283
|
+
if (payload.autoClose === false) {
|
|
284
|
+
hashParams.set("ydAutoClose", "false");
|
|
285
|
+
}
|
|
286
|
+
if (payload.origin) {
|
|
287
|
+
hashParams.set("ydOrigin", payload.origin);
|
|
288
|
+
}
|
|
289
|
+
if (payload.callbackUrl) {
|
|
290
|
+
hashParams.set("ydCallbackUrl", payload.callbackUrl);
|
|
291
|
+
}
|
|
292
|
+
if (payload.preferredChannel) {
|
|
293
|
+
hashParams.set("ydPreferredChannel", payload.preferredChannel);
|
|
294
|
+
}
|
|
295
|
+
const hash = hashParams.toString();
|
|
296
|
+
if (!hash) {
|
|
297
|
+
return urlValue;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const url = new URL(urlValue);
|
|
301
|
+
url.hash = hash;
|
|
302
|
+
return url.toString();
|
|
303
|
+
} catch (_error) {
|
|
304
|
+
return `${urlValue}#${hash}`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
function buildLoginPopupName(payload) {
|
|
308
|
+
return `youidian-login:${JSON.stringify(payload)}`;
|
|
309
|
+
}
|
|
310
|
+
function buildLoginUrl(params, options, launchPayload) {
|
|
359
311
|
const { appId, baseUrl = "https://pay.imgto.link", loginUrl } = params;
|
|
360
312
|
const rawBase = (loginUrl || baseUrl).replace(/\/$/, "");
|
|
361
|
-
const parentOrigin =
|
|
313
|
+
const parentOrigin = launchPayload.origin;
|
|
362
314
|
let finalUrl;
|
|
363
315
|
try {
|
|
364
316
|
const url = new URL(rawBase);
|
|
@@ -382,24 +334,39 @@ function buildLoginUrl(params) {
|
|
|
382
334
|
if (params.preferredChannel) {
|
|
383
335
|
url.searchParams.set("preferredChannel", params.preferredChannel);
|
|
384
336
|
}
|
|
337
|
+
if (options?.autoClose === false) {
|
|
338
|
+
url.searchParams.set("autoClose", "false");
|
|
339
|
+
}
|
|
385
340
|
if (parentOrigin) {
|
|
386
341
|
url.searchParams.set("origin", parentOrigin);
|
|
387
342
|
}
|
|
388
|
-
|
|
343
|
+
if (launchPayload.callbackUrl) {
|
|
344
|
+
url.searchParams.set("callbackUrl", launchPayload.callbackUrl);
|
|
345
|
+
}
|
|
346
|
+
return appendLoginLaunchHash(url.toString(), launchPayload);
|
|
389
347
|
} catch (_error) {
|
|
390
348
|
const searchParams = new URLSearchParams();
|
|
391
349
|
if (params.preferredChannel) {
|
|
392
350
|
searchParams.set("preferredChannel", params.preferredChannel);
|
|
393
351
|
}
|
|
352
|
+
if (options?.autoClose === false) {
|
|
353
|
+
searchParams.set("autoClose", "false");
|
|
354
|
+
}
|
|
394
355
|
if (parentOrigin) {
|
|
395
356
|
searchParams.set("origin", parentOrigin);
|
|
396
357
|
}
|
|
358
|
+
if (launchPayload.callbackUrl) {
|
|
359
|
+
searchParams.set("callbackUrl", launchPayload.callbackUrl);
|
|
360
|
+
}
|
|
397
361
|
const query = searchParams.toString();
|
|
398
362
|
if (!query) {
|
|
399
|
-
return finalUrl;
|
|
363
|
+
return appendLoginLaunchHash(finalUrl, launchPayload);
|
|
400
364
|
}
|
|
401
365
|
const separator = finalUrl.includes("?") ? "&" : "?";
|
|
402
|
-
return
|
|
366
|
+
return appendLoginLaunchHash(
|
|
367
|
+
`${finalUrl}${separator}${query}`,
|
|
368
|
+
launchPayload
|
|
369
|
+
);
|
|
403
370
|
}
|
|
404
371
|
}
|
|
405
372
|
function extractLoginResult(data) {
|
|
@@ -417,6 +384,102 @@ function extractLoginResult(data) {
|
|
|
417
384
|
};
|
|
418
385
|
}
|
|
419
386
|
var LOGIN_UI_LOG_PREFIX = "[Youidian LoginUI]";
|
|
387
|
+
var LOGIN_CALLBACK_MARKER = "yd_login_callback";
|
|
388
|
+
var LOGIN_CALLBACK_RESULT_PARAM = "yd_login_result";
|
|
389
|
+
var LOGIN_CALLBACK_RETURN_HASH_PARAM = "yd_login_return_hash";
|
|
390
|
+
var LOGIN_CALLBACK_STATE_PARAM = "yd_login_state";
|
|
391
|
+
var LOGIN_CALLBACK_STORAGE_PREFIX = "yd-login-callback:";
|
|
392
|
+
function decodeCallbackPayload(value) {
|
|
393
|
+
try {
|
|
394
|
+
const padded = value.padEnd(
|
|
395
|
+
value.length + (4 - value.length % 4) % 4,
|
|
396
|
+
"="
|
|
397
|
+
);
|
|
398
|
+
const base64 = padded.replaceAll("-", "+").replaceAll("_", "/");
|
|
399
|
+
const binary = atob(base64);
|
|
400
|
+
const json = decodeURIComponent(
|
|
401
|
+
Array.from(
|
|
402
|
+
binary,
|
|
403
|
+
(char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`
|
|
404
|
+
).join("")
|
|
405
|
+
);
|
|
406
|
+
const parsed = JSON.parse(json);
|
|
407
|
+
return parsed && typeof parsed === "object" && parsed.type ? parsed : null;
|
|
408
|
+
} catch {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
function getLoginCallbackChannelName(state) {
|
|
413
|
+
return `youidian-login:${state}`;
|
|
414
|
+
}
|
|
415
|
+
function getLoginCallbackStorageKey(state) {
|
|
416
|
+
return `${LOGIN_CALLBACK_STORAGE_PREFIX}${state}`;
|
|
417
|
+
}
|
|
418
|
+
function publishLoginCallback(state, data) {
|
|
419
|
+
try {
|
|
420
|
+
const channel = new BroadcastChannel(getLoginCallbackChannelName(state));
|
|
421
|
+
channel.postMessage(data);
|
|
422
|
+
channel.close();
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
try {
|
|
426
|
+
const storageKey = getLoginCallbackStorageKey(state);
|
|
427
|
+
window.localStorage.setItem(storageKey, JSON.stringify(data));
|
|
428
|
+
window.setTimeout(() => {
|
|
429
|
+
try {
|
|
430
|
+
window.localStorage.removeItem(storageKey);
|
|
431
|
+
} catch {
|
|
432
|
+
}
|
|
433
|
+
}, 800);
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
try {
|
|
437
|
+
window.opener?.postMessage(data, window.location.origin);
|
|
438
|
+
} catch {
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function cleanLoginCallbackUrl(url) {
|
|
442
|
+
url.searchParams.delete(LOGIN_CALLBACK_MARKER);
|
|
443
|
+
url.searchParams.delete(LOGIN_CALLBACK_STATE_PARAM);
|
|
444
|
+
const returnHash = url.searchParams.get(LOGIN_CALLBACK_RETURN_HASH_PARAM);
|
|
445
|
+
url.searchParams.delete(LOGIN_CALLBACK_RETURN_HASH_PARAM);
|
|
446
|
+
url.hash = returnHash ? `#${returnHash}` : "";
|
|
447
|
+
return url.toString();
|
|
448
|
+
}
|
|
449
|
+
function handleLoginCallbackIfPresent() {
|
|
450
|
+
if (typeof window === "undefined") {
|
|
451
|
+
return false;
|
|
452
|
+
}
|
|
453
|
+
const url = new URL(window.location.href);
|
|
454
|
+
if (url.searchParams.get(LOGIN_CALLBACK_MARKER) !== "1") {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
const state = url.searchParams.get(LOGIN_CALLBACK_STATE_PARAM) || "";
|
|
458
|
+
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ""));
|
|
459
|
+
const rawResult = hashParams.get(LOGIN_CALLBACK_RESULT_PARAM);
|
|
460
|
+
const data = rawResult && state ? decodeCallbackPayload(rawResult) : {
|
|
461
|
+
type: "LOGIN_ERROR",
|
|
462
|
+
message: "Missing login callback result"
|
|
463
|
+
};
|
|
464
|
+
try {
|
|
465
|
+
document.documentElement.style.visibility = "hidden";
|
|
466
|
+
} catch {
|
|
467
|
+
}
|
|
468
|
+
if (state && data) {
|
|
469
|
+
publishLoginCallback(state, data);
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
window.history.replaceState(null, "", cleanLoginCallbackUrl(url));
|
|
473
|
+
} catch {
|
|
474
|
+
}
|
|
475
|
+
window.setTimeout(() => {
|
|
476
|
+
try {
|
|
477
|
+
window.close();
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
}, 30);
|
|
481
|
+
return true;
|
|
482
|
+
}
|
|
420
483
|
function logLoginDebug(message, details) {
|
|
421
484
|
if (typeof console === "undefined") return;
|
|
422
485
|
console.debug(LOGIN_UI_LOG_PREFIX, message, details || {});
|
|
@@ -432,14 +495,24 @@ function logLoginWarn(message, details) {
|
|
|
432
495
|
var LoginUI = class {
|
|
433
496
|
constructor() {
|
|
434
497
|
__publicField(this, "popup", null);
|
|
498
|
+
__publicField(this, "callbackChannel", null);
|
|
499
|
+
__publicField(this, "callbackStorageHandler", null);
|
|
435
500
|
__publicField(this, "messageHandler", null);
|
|
436
501
|
__publicField(this, "closeMonitor", null);
|
|
437
502
|
__publicField(this, "completed", false);
|
|
438
503
|
}
|
|
439
504
|
cleanup() {
|
|
505
|
+
if (this.callbackChannel) {
|
|
506
|
+
this.callbackChannel.close();
|
|
507
|
+
}
|
|
508
|
+
if (typeof window !== "undefined" && this.callbackStorageHandler) {
|
|
509
|
+
window.removeEventListener("storage", this.callbackStorageHandler);
|
|
510
|
+
}
|
|
440
511
|
if (typeof window !== "undefined" && this.messageHandler) {
|
|
441
512
|
window.removeEventListener("message", this.messageHandler);
|
|
442
513
|
}
|
|
514
|
+
this.callbackChannel = null;
|
|
515
|
+
this.callbackStorageHandler = null;
|
|
443
516
|
if (typeof window !== "undefined" && this.closeMonitor) {
|
|
444
517
|
window.clearInterval(this.closeMonitor);
|
|
445
518
|
}
|
|
@@ -448,6 +521,81 @@ var LoginUI = class {
|
|
|
448
521
|
this.popup = null;
|
|
449
522
|
this.completed = false;
|
|
450
523
|
}
|
|
524
|
+
handleLoginEvent(data, options) {
|
|
525
|
+
if (!data || typeof data !== "object" || !data.type) {
|
|
526
|
+
logLoginDebug("Ignored non-login event payload");
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
logLoginInfo("Received login event", {
|
|
530
|
+
type: data.type
|
|
531
|
+
});
|
|
532
|
+
switch (data.type) {
|
|
533
|
+
case "LOGIN_SUCCESS":
|
|
534
|
+
if (this.completed) {
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
this.completed = true;
|
|
538
|
+
logLoginInfo("Login succeeded", {
|
|
539
|
+
channel: data.channel || null,
|
|
540
|
+
userId: data.userId || null
|
|
541
|
+
});
|
|
542
|
+
options?.onSuccess?.(extractLoginResult(data));
|
|
543
|
+
break;
|
|
544
|
+
case "LOGIN_CANCELLED":
|
|
545
|
+
logLoginInfo("Login cancelled");
|
|
546
|
+
options?.onCancel?.();
|
|
547
|
+
break;
|
|
548
|
+
case "LOGIN_RESIZE":
|
|
549
|
+
break;
|
|
550
|
+
case "LOGIN_ERROR":
|
|
551
|
+
if (this.completed) {
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
logLoginWarn("Login flow reported an error", {
|
|
555
|
+
message: data.message || data.error || null
|
|
556
|
+
});
|
|
557
|
+
options?.onError?.(data.message || data.error, data);
|
|
558
|
+
break;
|
|
559
|
+
case "LOGIN_CLOSE":
|
|
560
|
+
if (options?.autoClose === false) {
|
|
561
|
+
logLoginInfo(
|
|
562
|
+
"Login popup requested close; autoClose disabled, keeping popup open"
|
|
563
|
+
);
|
|
564
|
+
options?.onClose?.();
|
|
565
|
+
break;
|
|
566
|
+
}
|
|
567
|
+
logLoginInfo("Login popup requested close; autoClose enabled");
|
|
568
|
+
this.close();
|
|
569
|
+
options?.onClose?.();
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
listenForLoginCallback(callbackState, options) {
|
|
574
|
+
try {
|
|
575
|
+
this.callbackChannel = new BroadcastChannel(
|
|
576
|
+
getLoginCallbackChannelName(callbackState)
|
|
577
|
+
);
|
|
578
|
+
this.callbackChannel.onmessage = (event) => {
|
|
579
|
+
this.handleLoginEvent(event.data, options);
|
|
580
|
+
this.handleLoginEvent({ type: "LOGIN_CLOSE" }, options);
|
|
581
|
+
};
|
|
582
|
+
} catch {
|
|
583
|
+
logLoginDebug("BroadcastChannel is unavailable for login callback");
|
|
584
|
+
}
|
|
585
|
+
this.callbackStorageHandler = (event) => {
|
|
586
|
+
if (event.key !== getLoginCallbackStorageKey(callbackState) || !event.newValue) {
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
try {
|
|
590
|
+
const data = JSON.parse(event.newValue);
|
|
591
|
+
this.handleLoginEvent(data, options);
|
|
592
|
+
this.handleLoginEvent({ type: "LOGIN_CLOSE" }, options);
|
|
593
|
+
} catch {
|
|
594
|
+
logLoginWarn("Failed to parse login callback storage payload");
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
window.addEventListener("storage", this.callbackStorageHandler);
|
|
598
|
+
}
|
|
451
599
|
close() {
|
|
452
600
|
logLoginInfo("close() called", {
|
|
453
601
|
hasPopup: Boolean(this.popup),
|
|
@@ -461,7 +609,13 @@ var LoginUI = class {
|
|
|
461
609
|
openLogin(params, options) {
|
|
462
610
|
if (typeof window === "undefined") return;
|
|
463
611
|
if (this.popup && !this.popup.closed) return;
|
|
464
|
-
const
|
|
612
|
+
const callbackState = createLoginCallbackState();
|
|
613
|
+
const launchPayload = buildLoginLaunchPayload(
|
|
614
|
+
params,
|
|
615
|
+
options,
|
|
616
|
+
callbackState
|
|
617
|
+
);
|
|
618
|
+
const finalUrl = buildLoginUrl(params, options, launchPayload);
|
|
465
619
|
logLoginInfo("Opening hosted login popup", {
|
|
466
620
|
appId: params.appId,
|
|
467
621
|
loginUrl: finalUrl,
|
|
@@ -487,7 +641,8 @@ var LoginUI = class {
|
|
|
487
641
|
"resizable=yes",
|
|
488
642
|
"scrollbars=yes"
|
|
489
643
|
].join(",");
|
|
490
|
-
const
|
|
644
|
+
const popupName = buildLoginPopupName(launchPayload);
|
|
645
|
+
const popup = window.open(finalUrl, popupName, features);
|
|
491
646
|
if (!popup) {
|
|
492
647
|
logLoginWarn("Popup blocked by the browser");
|
|
493
648
|
options?.onError?.("Login popup was blocked");
|
|
@@ -495,6 +650,7 @@ var LoginUI = class {
|
|
|
495
650
|
}
|
|
496
651
|
this.popup = popup;
|
|
497
652
|
this.completed = false;
|
|
653
|
+
this.listenForLoginCallback(callbackState, options);
|
|
498
654
|
const allowedOrigin = options?.allowedOrigin;
|
|
499
655
|
const popupOrigin = getOrigin(finalUrl);
|
|
500
656
|
this.messageHandler = (event) => {
|
|
@@ -521,38 +677,7 @@ var LoginUI = class {
|
|
|
521
677
|
type: data.type,
|
|
522
678
|
eventOrigin: event.origin
|
|
523
679
|
});
|
|
524
|
-
|
|
525
|
-
case "LOGIN_SUCCESS":
|
|
526
|
-
this.completed = true;
|
|
527
|
-
logLoginInfo("Login succeeded", {
|
|
528
|
-
channel: data.channel || null,
|
|
529
|
-
userId: data.userId || null
|
|
530
|
-
});
|
|
531
|
-
options?.onSuccess?.(extractLoginResult(data));
|
|
532
|
-
break;
|
|
533
|
-
case "LOGIN_CANCELLED":
|
|
534
|
-
logLoginInfo("Login cancelled");
|
|
535
|
-
options?.onCancel?.();
|
|
536
|
-
break;
|
|
537
|
-
case "LOGIN_RESIZE":
|
|
538
|
-
break;
|
|
539
|
-
case "LOGIN_ERROR":
|
|
540
|
-
logLoginWarn("Login flow reported an error", {
|
|
541
|
-
message: data.message || data.error || null
|
|
542
|
-
});
|
|
543
|
-
options?.onError?.(data.message || data.error, data);
|
|
544
|
-
break;
|
|
545
|
-
case "LOGIN_CLOSE":
|
|
546
|
-
if (options?.autoClose === false) {
|
|
547
|
-
logLoginInfo("Login popup requested close; autoClose disabled, keeping popup open");
|
|
548
|
-
options?.onClose?.();
|
|
549
|
-
break;
|
|
550
|
-
}
|
|
551
|
-
logLoginInfo("Login popup requested close; autoClose enabled");
|
|
552
|
-
this.close();
|
|
553
|
-
options?.onClose?.();
|
|
554
|
-
break;
|
|
555
|
-
}
|
|
680
|
+
this.handleLoginEvent(data, options);
|
|
556
681
|
};
|
|
557
682
|
window.addEventListener("message", this.messageHandler);
|
|
558
683
|
this.closeMonitor = window.setInterval(() => {
|
|
@@ -573,6 +698,116 @@ var LoginUI = class {
|
|
|
573
698
|
function createLoginUI() {
|
|
574
699
|
return new LoginUI();
|
|
575
700
|
}
|
|
701
|
+
handleLoginCallbackIfPresent();
|
|
702
|
+
|
|
703
|
+
// src/client.ts
|
|
704
|
+
var PaymentUI = class extends HostedFrameModal {
|
|
705
|
+
/**
|
|
706
|
+
* Opens the payment checkout page in an iframe modal.
|
|
707
|
+
* @param urlOrParams - The checkout page URL or payment parameters
|
|
708
|
+
* @param options - UI options
|
|
709
|
+
*/
|
|
710
|
+
openPayment(urlOrParams, options) {
|
|
711
|
+
if (typeof document === "undefined") return;
|
|
712
|
+
if (this.modal) return;
|
|
713
|
+
let checkoutUrl;
|
|
714
|
+
if (typeof urlOrParams === "string") {
|
|
715
|
+
checkoutUrl = urlOrParams;
|
|
716
|
+
} else {
|
|
717
|
+
const {
|
|
718
|
+
appId,
|
|
719
|
+
productId,
|
|
720
|
+
priceId,
|
|
721
|
+
productCode,
|
|
722
|
+
userId,
|
|
723
|
+
checkoutUrl: checkoutUrlParam,
|
|
724
|
+
baseUrl = "https://pay.imgto.link"
|
|
725
|
+
} = urlOrParams;
|
|
726
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
727
|
+
if (productCode) {
|
|
728
|
+
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
729
|
+
} else if (productId && priceId) {
|
|
730
|
+
checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
|
|
731
|
+
} else {
|
|
732
|
+
throw new Error(
|
|
733
|
+
"Either productCode or both productId and priceId are required"
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
|
|
738
|
+
this.openHostedFrame(finalUrl, {
|
|
739
|
+
allowedOrigin: options?.allowedOrigin,
|
|
740
|
+
onCloseButton: () => options?.onCancel?.(),
|
|
741
|
+
onMessage: (data, container) => {
|
|
742
|
+
switch (data.type) {
|
|
743
|
+
case "PAYMENT_SUCCESS":
|
|
744
|
+
options?.onSuccess?.(data.orderId);
|
|
745
|
+
break;
|
|
746
|
+
case "PAYMENT_CANCELLED":
|
|
747
|
+
options?.onCancel?.(data.orderId);
|
|
748
|
+
break;
|
|
749
|
+
case "PAYMENT_RESIZE":
|
|
750
|
+
if (data.height) {
|
|
751
|
+
const maxHeight = window.innerHeight * 0.9;
|
|
752
|
+
const newHeight = Math.min(data.height, maxHeight);
|
|
753
|
+
container.style.height = `${newHeight}px`;
|
|
754
|
+
}
|
|
755
|
+
break;
|
|
756
|
+
case "PAYMENT_CLOSE":
|
|
757
|
+
this.close();
|
|
758
|
+
options?.onClose?.();
|
|
759
|
+
break;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Poll order status from integrator's API endpoint
|
|
766
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
767
|
+
* @param options - Polling options
|
|
768
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
769
|
+
*/
|
|
770
|
+
async pollOrderStatus(statusUrl, options) {
|
|
771
|
+
const interval = options?.interval || 3e3;
|
|
772
|
+
const timeout = options?.timeout || 3e5;
|
|
773
|
+
const startTime = Date.now();
|
|
774
|
+
return new Promise((resolve, reject) => {
|
|
775
|
+
const poll = async () => {
|
|
776
|
+
try {
|
|
777
|
+
const response = await fetch(statusUrl);
|
|
778
|
+
if (!response.ok) {
|
|
779
|
+
throw new Error(`Status check failed: ${response.status}`);
|
|
780
|
+
}
|
|
781
|
+
const status = await response.json();
|
|
782
|
+
options?.onStatusChange?.(status);
|
|
783
|
+
if (status.status === "PAID") {
|
|
784
|
+
resolve(status);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
788
|
+
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (Date.now() - startTime > timeout) {
|
|
792
|
+
reject(new Error("Polling timeout"));
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
setTimeout(poll, interval);
|
|
796
|
+
} catch (error) {
|
|
797
|
+
if (Date.now() - startTime > timeout) {
|
|
798
|
+
reject(error);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
setTimeout(poll, interval);
|
|
802
|
+
}
|
|
803
|
+
};
|
|
804
|
+
poll();
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
function createPaymentUI() {
|
|
809
|
+
return new PaymentUI();
|
|
810
|
+
}
|
|
576
811
|
|
|
577
812
|
// src/server.ts
|
|
578
813
|
var import_crypto = __toESM(require("crypto"), 1);
|
|
@@ -830,6 +1065,7 @@ var PaymentClient = class {
|
|
|
830
1065
|
PaymentClient,
|
|
831
1066
|
PaymentUI,
|
|
832
1067
|
createLoginUI,
|
|
833
|
-
createPaymentUI
|
|
1068
|
+
createPaymentUI,
|
|
1069
|
+
handleLoginCallbackIfPresent
|
|
834
1070
|
});
|
|
835
1071
|
//# sourceMappingURL=index.cjs.map
|