@youidian/sdk 3.1.1 → 3.2.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/dist/client.cjs +552 -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 +548 -1
- package/dist/client.js.map +1 -1
- package/dist/index.cjs +345 -153
- 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 +347 -156
- 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 +236 -44
- 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 +234 -43
- package/dist/login.js.map +1 -1
- package/package.json +1 -10
- package/dist/hosted-modal-BZmYmXTU.d.cts +0 -20
- package/dist/hosted-modal-BZmYmXTU.d.ts +0 -20
package/dist/index.js
CHANGED
|
@@ -199,115 +199,6 @@ var HostedFrameModal = class {
|
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
-
// src/client.ts
|
|
203
|
-
var PaymentUI = class extends HostedFrameModal {
|
|
204
|
-
/**
|
|
205
|
-
* Opens the payment checkout page in an iframe modal.
|
|
206
|
-
* @param urlOrParams - The checkout page URL or payment parameters
|
|
207
|
-
* @param options - UI options
|
|
208
|
-
*/
|
|
209
|
-
openPayment(urlOrParams, options) {
|
|
210
|
-
if (typeof document === "undefined") return;
|
|
211
|
-
if (this.modal) return;
|
|
212
|
-
let checkoutUrl;
|
|
213
|
-
if (typeof urlOrParams === "string") {
|
|
214
|
-
checkoutUrl = urlOrParams;
|
|
215
|
-
} else {
|
|
216
|
-
const {
|
|
217
|
-
appId,
|
|
218
|
-
productId,
|
|
219
|
-
priceId,
|
|
220
|
-
productCode,
|
|
221
|
-
userId,
|
|
222
|
-
checkoutUrl: checkoutUrlParam,
|
|
223
|
-
baseUrl = "https://pay.imgto.link"
|
|
224
|
-
} = urlOrParams;
|
|
225
|
-
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
226
|
-
if (productCode) {
|
|
227
|
-
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
228
|
-
} else if (productId && priceId) {
|
|
229
|
-
checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
|
|
230
|
-
} else {
|
|
231
|
-
throw new Error(
|
|
232
|
-
"Either productCode or both productId and priceId are required"
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
|
|
237
|
-
this.openHostedFrame(finalUrl, {
|
|
238
|
-
allowedOrigin: options?.allowedOrigin,
|
|
239
|
-
onCloseButton: () => options?.onCancel?.(),
|
|
240
|
-
onMessage: (data, container) => {
|
|
241
|
-
switch (data.type) {
|
|
242
|
-
case "PAYMENT_SUCCESS":
|
|
243
|
-
options?.onSuccess?.(data.orderId);
|
|
244
|
-
break;
|
|
245
|
-
case "PAYMENT_CANCELLED":
|
|
246
|
-
options?.onCancel?.(data.orderId);
|
|
247
|
-
break;
|
|
248
|
-
case "PAYMENT_RESIZE":
|
|
249
|
-
if (data.height) {
|
|
250
|
-
const maxHeight = window.innerHeight * 0.9;
|
|
251
|
-
const newHeight = Math.min(data.height, maxHeight);
|
|
252
|
-
container.style.height = `${newHeight}px`;
|
|
253
|
-
}
|
|
254
|
-
break;
|
|
255
|
-
case "PAYMENT_CLOSE":
|
|
256
|
-
this.close();
|
|
257
|
-
options?.onClose?.();
|
|
258
|
-
break;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
/**
|
|
264
|
-
* Poll order status from integrator's API endpoint
|
|
265
|
-
* @param statusUrl - The integrator's API endpoint to check order status
|
|
266
|
-
* @param options - Polling options
|
|
267
|
-
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
268
|
-
*/
|
|
269
|
-
async pollOrderStatus(statusUrl, options) {
|
|
270
|
-
const interval = options?.interval || 3e3;
|
|
271
|
-
const timeout = options?.timeout || 3e5;
|
|
272
|
-
const startTime = Date.now();
|
|
273
|
-
return new Promise((resolve, reject) => {
|
|
274
|
-
const poll = async () => {
|
|
275
|
-
try {
|
|
276
|
-
const response = await fetch(statusUrl);
|
|
277
|
-
if (!response.ok) {
|
|
278
|
-
throw new Error(`Status check failed: ${response.status}`);
|
|
279
|
-
}
|
|
280
|
-
const status = await response.json();
|
|
281
|
-
options?.onStatusChange?.(status);
|
|
282
|
-
if (status.status === "PAID") {
|
|
283
|
-
resolve(status);
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
287
|
-
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
if (Date.now() - startTime > timeout) {
|
|
291
|
-
reject(new Error("Polling timeout"));
|
|
292
|
-
return;
|
|
293
|
-
}
|
|
294
|
-
setTimeout(poll, interval);
|
|
295
|
-
} catch (error) {
|
|
296
|
-
if (Date.now() - startTime > timeout) {
|
|
297
|
-
reject(error);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
setTimeout(poll, interval);
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
poll();
|
|
304
|
-
});
|
|
305
|
-
}
|
|
306
|
-
};
|
|
307
|
-
function createPaymentUI() {
|
|
308
|
-
return new PaymentUI();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
202
|
// src/login.ts
|
|
312
203
|
function getOrigin(value) {
|
|
313
204
|
if (!value) return null;
|
|
@@ -317,10 +208,33 @@ function getOrigin(value) {
|
|
|
317
208
|
return null;
|
|
318
209
|
}
|
|
319
210
|
}
|
|
320
|
-
function
|
|
211
|
+
function createLoginCallbackState() {
|
|
212
|
+
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
|
213
|
+
return crypto.randomUUID().replace(/-/g, "");
|
|
214
|
+
}
|
|
215
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
|
|
216
|
+
}
|
|
217
|
+
function buildLoginCallbackUrl(callbackState, callbackUrl) {
|
|
218
|
+
const baseUrl = callbackUrl || window.location.href;
|
|
219
|
+
const url = new URL(baseUrl, window.location.href);
|
|
220
|
+
const returnHash = url.hash.replace(/^#/, "");
|
|
221
|
+
url.hash = "";
|
|
222
|
+
url.searchParams.set(LOGIN_CALLBACK_MARKER, "1");
|
|
223
|
+
url.searchParams.set(LOGIN_CALLBACK_STATE_PARAM, callbackState);
|
|
224
|
+
if (returnHash) {
|
|
225
|
+
url.searchParams.set(LOGIN_CALLBACK_RETURN_HASH_PARAM, returnHash);
|
|
226
|
+
} else {
|
|
227
|
+
url.searchParams.delete(LOGIN_CALLBACK_RETURN_HASH_PARAM);
|
|
228
|
+
}
|
|
229
|
+
return url.toString();
|
|
230
|
+
}
|
|
231
|
+
function buildLoginLaunchPayload(params, options, callbackState) {
|
|
321
232
|
const parentOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
|
|
233
|
+
const callbackUrl = typeof window !== "undefined" && callbackState ? buildLoginCallbackUrl(callbackState, options?.callbackUrl) : void 0;
|
|
322
234
|
return {
|
|
323
235
|
autoClose: options?.autoClose ?? true,
|
|
236
|
+
callbackUrl,
|
|
237
|
+
callbackState,
|
|
324
238
|
origin: parentOrigin,
|
|
325
239
|
preferredChannel: params.preferredChannel
|
|
326
240
|
};
|
|
@@ -333,6 +247,9 @@ function appendLoginLaunchHash(urlValue, payload) {
|
|
|
333
247
|
if (payload.origin) {
|
|
334
248
|
hashParams.set("ydOrigin", payload.origin);
|
|
335
249
|
}
|
|
250
|
+
if (payload.callbackUrl) {
|
|
251
|
+
hashParams.set("ydCallbackUrl", payload.callbackUrl);
|
|
252
|
+
}
|
|
336
253
|
if (payload.preferredChannel) {
|
|
337
254
|
hashParams.set("ydPreferredChannel", payload.preferredChannel);
|
|
338
255
|
}
|
|
@@ -351,10 +268,9 @@ function appendLoginLaunchHash(urlValue, payload) {
|
|
|
351
268
|
function buildLoginPopupName(payload) {
|
|
352
269
|
return `youidian-login:${JSON.stringify(payload)}`;
|
|
353
270
|
}
|
|
354
|
-
function buildLoginUrl(params, options) {
|
|
271
|
+
function buildLoginUrl(params, options, launchPayload) {
|
|
355
272
|
const { appId, baseUrl = "https://pay.imgto.link", loginUrl } = params;
|
|
356
273
|
const rawBase = (loginUrl || baseUrl).replace(/\/$/, "");
|
|
357
|
-
const launchPayload = buildLoginLaunchPayload(params, options);
|
|
358
274
|
const parentOrigin = launchPayload.origin;
|
|
359
275
|
let finalUrl;
|
|
360
276
|
try {
|
|
@@ -385,6 +301,9 @@ function buildLoginUrl(params, options) {
|
|
|
385
301
|
if (parentOrigin) {
|
|
386
302
|
url.searchParams.set("origin", parentOrigin);
|
|
387
303
|
}
|
|
304
|
+
if (launchPayload.callbackUrl) {
|
|
305
|
+
url.searchParams.set("callbackUrl", launchPayload.callbackUrl);
|
|
306
|
+
}
|
|
388
307
|
return appendLoginLaunchHash(url.toString(), launchPayload);
|
|
389
308
|
} catch (_error) {
|
|
390
309
|
const searchParams = new URLSearchParams();
|
|
@@ -397,6 +316,9 @@ function buildLoginUrl(params, options) {
|
|
|
397
316
|
if (parentOrigin) {
|
|
398
317
|
searchParams.set("origin", parentOrigin);
|
|
399
318
|
}
|
|
319
|
+
if (launchPayload.callbackUrl) {
|
|
320
|
+
searchParams.set("callbackUrl", launchPayload.callbackUrl);
|
|
321
|
+
}
|
|
400
322
|
const query = searchParams.toString();
|
|
401
323
|
if (!query) {
|
|
402
324
|
return appendLoginLaunchHash(finalUrl, launchPayload);
|
|
@@ -423,6 +345,102 @@ function extractLoginResult(data) {
|
|
|
423
345
|
};
|
|
424
346
|
}
|
|
425
347
|
var LOGIN_UI_LOG_PREFIX = "[Youidian LoginUI]";
|
|
348
|
+
var LOGIN_CALLBACK_MARKER = "yd_login_callback";
|
|
349
|
+
var LOGIN_CALLBACK_RESULT_PARAM = "yd_login_result";
|
|
350
|
+
var LOGIN_CALLBACK_RETURN_HASH_PARAM = "yd_login_return_hash";
|
|
351
|
+
var LOGIN_CALLBACK_STATE_PARAM = "yd_login_state";
|
|
352
|
+
var LOGIN_CALLBACK_STORAGE_PREFIX = "yd-login-callback:";
|
|
353
|
+
function decodeCallbackPayload(value) {
|
|
354
|
+
try {
|
|
355
|
+
const padded = value.padEnd(
|
|
356
|
+
value.length + (4 - value.length % 4) % 4,
|
|
357
|
+
"="
|
|
358
|
+
);
|
|
359
|
+
const base64 = padded.replaceAll("-", "+").replaceAll("_", "/");
|
|
360
|
+
const binary = atob(base64);
|
|
361
|
+
const json = decodeURIComponent(
|
|
362
|
+
Array.from(
|
|
363
|
+
binary,
|
|
364
|
+
(char) => `%${char.charCodeAt(0).toString(16).padStart(2, "0")}`
|
|
365
|
+
).join("")
|
|
366
|
+
);
|
|
367
|
+
const parsed = JSON.parse(json);
|
|
368
|
+
return parsed && typeof parsed === "object" && parsed.type ? parsed : null;
|
|
369
|
+
} catch {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function getLoginCallbackChannelName(state) {
|
|
374
|
+
return `youidian-login:${state}`;
|
|
375
|
+
}
|
|
376
|
+
function getLoginCallbackStorageKey(state) {
|
|
377
|
+
return `${LOGIN_CALLBACK_STORAGE_PREFIX}${state}`;
|
|
378
|
+
}
|
|
379
|
+
function publishLoginCallback(state, data) {
|
|
380
|
+
try {
|
|
381
|
+
const channel = new BroadcastChannel(getLoginCallbackChannelName(state));
|
|
382
|
+
channel.postMessage(data);
|
|
383
|
+
channel.close();
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
const storageKey = getLoginCallbackStorageKey(state);
|
|
388
|
+
window.localStorage.setItem(storageKey, JSON.stringify(data));
|
|
389
|
+
window.setTimeout(() => {
|
|
390
|
+
try {
|
|
391
|
+
window.localStorage.removeItem(storageKey);
|
|
392
|
+
} catch {
|
|
393
|
+
}
|
|
394
|
+
}, 800);
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
try {
|
|
398
|
+
window.opener?.postMessage(data, window.location.origin);
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
function cleanLoginCallbackUrl(url) {
|
|
403
|
+
url.searchParams.delete(LOGIN_CALLBACK_MARKER);
|
|
404
|
+
url.searchParams.delete(LOGIN_CALLBACK_STATE_PARAM);
|
|
405
|
+
const returnHash = url.searchParams.get(LOGIN_CALLBACK_RETURN_HASH_PARAM);
|
|
406
|
+
url.searchParams.delete(LOGIN_CALLBACK_RETURN_HASH_PARAM);
|
|
407
|
+
url.hash = returnHash ? `#${returnHash}` : "";
|
|
408
|
+
return url.toString();
|
|
409
|
+
}
|
|
410
|
+
function handleLoginCallbackIfPresent() {
|
|
411
|
+
if (typeof window === "undefined") {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const url = new URL(window.location.href);
|
|
415
|
+
if (url.searchParams.get(LOGIN_CALLBACK_MARKER) !== "1") {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
const state = url.searchParams.get(LOGIN_CALLBACK_STATE_PARAM) || "";
|
|
419
|
+
const hashParams = new URLSearchParams(url.hash.replace(/^#/, ""));
|
|
420
|
+
const rawResult = hashParams.get(LOGIN_CALLBACK_RESULT_PARAM);
|
|
421
|
+
const data = rawResult && state ? decodeCallbackPayload(rawResult) : {
|
|
422
|
+
type: "LOGIN_ERROR",
|
|
423
|
+
message: "Missing login callback result"
|
|
424
|
+
};
|
|
425
|
+
try {
|
|
426
|
+
document.documentElement.style.visibility = "hidden";
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
if (state && data) {
|
|
430
|
+
publishLoginCallback(state, data);
|
|
431
|
+
}
|
|
432
|
+
try {
|
|
433
|
+
window.history.replaceState(null, "", cleanLoginCallbackUrl(url));
|
|
434
|
+
} catch {
|
|
435
|
+
}
|
|
436
|
+
window.setTimeout(() => {
|
|
437
|
+
try {
|
|
438
|
+
window.close();
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}, 30);
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
426
444
|
function logLoginDebug(message, details) {
|
|
427
445
|
if (typeof console === "undefined") return;
|
|
428
446
|
console.debug(LOGIN_UI_LOG_PREFIX, message, details || {});
|
|
@@ -438,14 +456,24 @@ function logLoginWarn(message, details) {
|
|
|
438
456
|
var LoginUI = class {
|
|
439
457
|
constructor() {
|
|
440
458
|
__publicField(this, "popup", null);
|
|
459
|
+
__publicField(this, "callbackChannel", null);
|
|
460
|
+
__publicField(this, "callbackStorageHandler", null);
|
|
441
461
|
__publicField(this, "messageHandler", null);
|
|
442
462
|
__publicField(this, "closeMonitor", null);
|
|
443
463
|
__publicField(this, "completed", false);
|
|
444
464
|
}
|
|
445
465
|
cleanup() {
|
|
466
|
+
if (this.callbackChannel) {
|
|
467
|
+
this.callbackChannel.close();
|
|
468
|
+
}
|
|
469
|
+
if (typeof window !== "undefined" && this.callbackStorageHandler) {
|
|
470
|
+
window.removeEventListener("storage", this.callbackStorageHandler);
|
|
471
|
+
}
|
|
446
472
|
if (typeof window !== "undefined" && this.messageHandler) {
|
|
447
473
|
window.removeEventListener("message", this.messageHandler);
|
|
448
474
|
}
|
|
475
|
+
this.callbackChannel = null;
|
|
476
|
+
this.callbackStorageHandler = null;
|
|
449
477
|
if (typeof window !== "undefined" && this.closeMonitor) {
|
|
450
478
|
window.clearInterval(this.closeMonitor);
|
|
451
479
|
}
|
|
@@ -454,6 +482,81 @@ var LoginUI = class {
|
|
|
454
482
|
this.popup = null;
|
|
455
483
|
this.completed = false;
|
|
456
484
|
}
|
|
485
|
+
handleLoginEvent(data, options) {
|
|
486
|
+
if (!data || typeof data !== "object" || !data.type) {
|
|
487
|
+
logLoginDebug("Ignored non-login event payload");
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
logLoginInfo("Received login event", {
|
|
491
|
+
type: data.type
|
|
492
|
+
});
|
|
493
|
+
switch (data.type) {
|
|
494
|
+
case "LOGIN_SUCCESS":
|
|
495
|
+
if (this.completed) {
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
this.completed = true;
|
|
499
|
+
logLoginInfo("Login succeeded", {
|
|
500
|
+
channel: data.channel || null,
|
|
501
|
+
userId: data.userId || null
|
|
502
|
+
});
|
|
503
|
+
options?.onSuccess?.(extractLoginResult(data));
|
|
504
|
+
break;
|
|
505
|
+
case "LOGIN_CANCELLED":
|
|
506
|
+
logLoginInfo("Login cancelled");
|
|
507
|
+
options?.onCancel?.();
|
|
508
|
+
break;
|
|
509
|
+
case "LOGIN_RESIZE":
|
|
510
|
+
break;
|
|
511
|
+
case "LOGIN_ERROR":
|
|
512
|
+
if (this.completed) {
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
logLoginWarn("Login flow reported an error", {
|
|
516
|
+
message: data.message || data.error || null
|
|
517
|
+
});
|
|
518
|
+
options?.onError?.(data.message || data.error, data);
|
|
519
|
+
break;
|
|
520
|
+
case "LOGIN_CLOSE":
|
|
521
|
+
if (options?.autoClose === false) {
|
|
522
|
+
logLoginInfo(
|
|
523
|
+
"Login popup requested close; autoClose disabled, keeping popup open"
|
|
524
|
+
);
|
|
525
|
+
options?.onClose?.();
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
logLoginInfo("Login popup requested close; autoClose enabled");
|
|
529
|
+
this.close();
|
|
530
|
+
options?.onClose?.();
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
listenForLoginCallback(callbackState, options) {
|
|
535
|
+
try {
|
|
536
|
+
this.callbackChannel = new BroadcastChannel(
|
|
537
|
+
getLoginCallbackChannelName(callbackState)
|
|
538
|
+
);
|
|
539
|
+
this.callbackChannel.onmessage = (event) => {
|
|
540
|
+
this.handleLoginEvent(event.data, options);
|
|
541
|
+
this.handleLoginEvent({ type: "LOGIN_CLOSE" }, options);
|
|
542
|
+
};
|
|
543
|
+
} catch {
|
|
544
|
+
logLoginDebug("BroadcastChannel is unavailable for login callback");
|
|
545
|
+
}
|
|
546
|
+
this.callbackStorageHandler = (event) => {
|
|
547
|
+
if (event.key !== getLoginCallbackStorageKey(callbackState) || !event.newValue) {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
const data = JSON.parse(event.newValue);
|
|
552
|
+
this.handleLoginEvent(data, options);
|
|
553
|
+
this.handleLoginEvent({ type: "LOGIN_CLOSE" }, options);
|
|
554
|
+
} catch {
|
|
555
|
+
logLoginWarn("Failed to parse login callback storage payload");
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
window.addEventListener("storage", this.callbackStorageHandler);
|
|
559
|
+
}
|
|
457
560
|
close() {
|
|
458
561
|
logLoginInfo("close() called", {
|
|
459
562
|
hasPopup: Boolean(this.popup),
|
|
@@ -467,12 +570,23 @@ var LoginUI = class {
|
|
|
467
570
|
openLogin(params, options) {
|
|
468
571
|
if (typeof window === "undefined") return;
|
|
469
572
|
if (this.popup && !this.popup.closed) return;
|
|
470
|
-
const
|
|
573
|
+
const callbackState = createLoginCallbackState();
|
|
574
|
+
const launchPayload = buildLoginLaunchPayload(
|
|
575
|
+
params,
|
|
576
|
+
options,
|
|
577
|
+
callbackState
|
|
578
|
+
);
|
|
579
|
+
const finalUrl = buildLoginUrl(params, options, launchPayload);
|
|
471
580
|
logLoginInfo("Opening hosted login popup", {
|
|
472
581
|
appId: params.appId,
|
|
582
|
+
callbackState,
|
|
583
|
+
callbackUrl: launchPayload.callbackUrl || null,
|
|
584
|
+
hasCallbackUrl: Boolean(launchPayload.callbackUrl),
|
|
473
585
|
loginUrl: finalUrl,
|
|
474
586
|
allowedOrigin: options?.allowedOrigin || null,
|
|
475
|
-
autoClose: options?.autoClose ?? true
|
|
587
|
+
autoClose: options?.autoClose ?? true,
|
|
588
|
+
pageUrl: typeof window !== "undefined" ? window.location.href : null,
|
|
589
|
+
parentOrigin: launchPayload.origin || null
|
|
476
590
|
});
|
|
477
591
|
const popupWidth = 520;
|
|
478
592
|
const popupHeight = 720;
|
|
@@ -493,9 +607,7 @@ var LoginUI = class {
|
|
|
493
607
|
"resizable=yes",
|
|
494
608
|
"scrollbars=yes"
|
|
495
609
|
].join(",");
|
|
496
|
-
const popupName = buildLoginPopupName(
|
|
497
|
-
buildLoginLaunchPayload(params, options)
|
|
498
|
-
);
|
|
610
|
+
const popupName = buildLoginPopupName(launchPayload);
|
|
499
611
|
const popup = window.open(finalUrl, popupName, features);
|
|
500
612
|
if (!popup) {
|
|
501
613
|
logLoginWarn("Popup blocked by the browser");
|
|
@@ -504,6 +616,7 @@ var LoginUI = class {
|
|
|
504
616
|
}
|
|
505
617
|
this.popup = popup;
|
|
506
618
|
this.completed = false;
|
|
619
|
+
this.listenForLoginCallback(callbackState, options);
|
|
507
620
|
const allowedOrigin = options?.allowedOrigin;
|
|
508
621
|
const popupOrigin = getOrigin(finalUrl);
|
|
509
622
|
this.messageHandler = (event) => {
|
|
@@ -530,40 +643,7 @@ var LoginUI = class {
|
|
|
530
643
|
type: data.type,
|
|
531
644
|
eventOrigin: event.origin
|
|
532
645
|
});
|
|
533
|
-
|
|
534
|
-
case "LOGIN_SUCCESS":
|
|
535
|
-
this.completed = true;
|
|
536
|
-
logLoginInfo("Login succeeded", {
|
|
537
|
-
channel: data.channel || null,
|
|
538
|
-
userId: data.userId || null
|
|
539
|
-
});
|
|
540
|
-
options?.onSuccess?.(extractLoginResult(data));
|
|
541
|
-
break;
|
|
542
|
-
case "LOGIN_CANCELLED":
|
|
543
|
-
logLoginInfo("Login cancelled");
|
|
544
|
-
options?.onCancel?.();
|
|
545
|
-
break;
|
|
546
|
-
case "LOGIN_RESIZE":
|
|
547
|
-
break;
|
|
548
|
-
case "LOGIN_ERROR":
|
|
549
|
-
logLoginWarn("Login flow reported an error", {
|
|
550
|
-
message: data.message || data.error || null
|
|
551
|
-
});
|
|
552
|
-
options?.onError?.(data.message || data.error, data);
|
|
553
|
-
break;
|
|
554
|
-
case "LOGIN_CLOSE":
|
|
555
|
-
if (options?.autoClose === false) {
|
|
556
|
-
logLoginInfo(
|
|
557
|
-
"Login popup requested close; autoClose disabled, keeping popup open"
|
|
558
|
-
);
|
|
559
|
-
options?.onClose?.();
|
|
560
|
-
break;
|
|
561
|
-
}
|
|
562
|
-
logLoginInfo("Login popup requested close; autoClose enabled");
|
|
563
|
-
this.close();
|
|
564
|
-
options?.onClose?.();
|
|
565
|
-
break;
|
|
566
|
-
}
|
|
646
|
+
this.handleLoginEvent(data, options);
|
|
567
647
|
};
|
|
568
648
|
window.addEventListener("message", this.messageHandler);
|
|
569
649
|
this.closeMonitor = window.setInterval(() => {
|
|
@@ -584,9 +664,119 @@ var LoginUI = class {
|
|
|
584
664
|
function createLoginUI() {
|
|
585
665
|
return new LoginUI();
|
|
586
666
|
}
|
|
667
|
+
handleLoginCallbackIfPresent();
|
|
668
|
+
|
|
669
|
+
// src/client.ts
|
|
670
|
+
var PaymentUI = class extends HostedFrameModal {
|
|
671
|
+
/**
|
|
672
|
+
* Opens the payment checkout page in an iframe modal.
|
|
673
|
+
* @param urlOrParams - The checkout page URL or payment parameters
|
|
674
|
+
* @param options - UI options
|
|
675
|
+
*/
|
|
676
|
+
openPayment(urlOrParams, options) {
|
|
677
|
+
if (typeof document === "undefined") return;
|
|
678
|
+
if (this.modal) return;
|
|
679
|
+
let checkoutUrl;
|
|
680
|
+
if (typeof urlOrParams === "string") {
|
|
681
|
+
checkoutUrl = urlOrParams;
|
|
682
|
+
} else {
|
|
683
|
+
const {
|
|
684
|
+
appId,
|
|
685
|
+
productId,
|
|
686
|
+
priceId,
|
|
687
|
+
productCode,
|
|
688
|
+
userId,
|
|
689
|
+
checkoutUrl: checkoutUrlParam,
|
|
690
|
+
baseUrl = "https://pay.imgto.link"
|
|
691
|
+
} = urlOrParams;
|
|
692
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
693
|
+
if (productCode) {
|
|
694
|
+
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
695
|
+
} else if (productId && priceId) {
|
|
696
|
+
checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
|
|
697
|
+
} else {
|
|
698
|
+
throw new Error(
|
|
699
|
+
"Either productCode or both productId and priceId are required"
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
|
|
704
|
+
this.openHostedFrame(finalUrl, {
|
|
705
|
+
allowedOrigin: options?.allowedOrigin,
|
|
706
|
+
onCloseButton: () => options?.onCancel?.(),
|
|
707
|
+
onMessage: (data, container) => {
|
|
708
|
+
switch (data.type) {
|
|
709
|
+
case "PAYMENT_SUCCESS":
|
|
710
|
+
options?.onSuccess?.(data.orderId);
|
|
711
|
+
break;
|
|
712
|
+
case "PAYMENT_CANCELLED":
|
|
713
|
+
options?.onCancel?.(data.orderId);
|
|
714
|
+
break;
|
|
715
|
+
case "PAYMENT_RESIZE":
|
|
716
|
+
if (data.height) {
|
|
717
|
+
const maxHeight = window.innerHeight * 0.9;
|
|
718
|
+
const newHeight = Math.min(data.height, maxHeight);
|
|
719
|
+
container.style.height = `${newHeight}px`;
|
|
720
|
+
}
|
|
721
|
+
break;
|
|
722
|
+
case "PAYMENT_CLOSE":
|
|
723
|
+
this.close();
|
|
724
|
+
options?.onClose?.();
|
|
725
|
+
break;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Poll order status from integrator's API endpoint
|
|
732
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
733
|
+
* @param options - Polling options
|
|
734
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
735
|
+
*/
|
|
736
|
+
async pollOrderStatus(statusUrl, options) {
|
|
737
|
+
const interval = options?.interval || 3e3;
|
|
738
|
+
const timeout = options?.timeout || 3e5;
|
|
739
|
+
const startTime = Date.now();
|
|
740
|
+
return new Promise((resolve, reject) => {
|
|
741
|
+
const poll = async () => {
|
|
742
|
+
try {
|
|
743
|
+
const response = await fetch(statusUrl);
|
|
744
|
+
if (!response.ok) {
|
|
745
|
+
throw new Error(`Status check failed: ${response.status}`);
|
|
746
|
+
}
|
|
747
|
+
const status = await response.json();
|
|
748
|
+
options?.onStatusChange?.(status);
|
|
749
|
+
if (status.status === "PAID") {
|
|
750
|
+
resolve(status);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
754
|
+
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (Date.now() - startTime > timeout) {
|
|
758
|
+
reject(new Error("Polling timeout"));
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
setTimeout(poll, interval);
|
|
762
|
+
} catch (error) {
|
|
763
|
+
if (Date.now() - startTime > timeout) {
|
|
764
|
+
reject(error);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
setTimeout(poll, interval);
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
poll();
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
function createPaymentUI() {
|
|
775
|
+
return new PaymentUI();
|
|
776
|
+
}
|
|
587
777
|
|
|
588
778
|
// src/server.ts
|
|
589
|
-
import
|
|
779
|
+
import crypto2 from "crypto";
|
|
590
780
|
var PaymentClient = class {
|
|
591
781
|
// 用于生成 checkout URL
|
|
592
782
|
constructor(options) {
|
|
@@ -610,7 +800,7 @@ var PaymentClient = class {
|
|
|
610
800
|
*/
|
|
611
801
|
generateSignature(timestamp) {
|
|
612
802
|
const str = `${this.appId}${this.appSecret}${timestamp}`;
|
|
613
|
-
return
|
|
803
|
+
return crypto2.createHash("sha256").update(str).digest("hex");
|
|
614
804
|
}
|
|
615
805
|
/**
|
|
616
806
|
* Internal request helper for Gateway API
|
|
@@ -657,8 +847,8 @@ var PaymentClient = class {
|
|
|
657
847
|
decryptCallback(notification) {
|
|
658
848
|
try {
|
|
659
849
|
const { iv, encryptedData, authTag } = notification;
|
|
660
|
-
const key =
|
|
661
|
-
const decipher =
|
|
850
|
+
const key = crypto2.createHash("sha256").update(this.appSecret).digest();
|
|
851
|
+
const decipher = crypto2.createDecipheriv(
|
|
662
852
|
"aes-256-gcm",
|
|
663
853
|
key,
|
|
664
854
|
Buffer.from(iv, "hex")
|
|
@@ -840,6 +1030,7 @@ export {
|
|
|
840
1030
|
PaymentClient,
|
|
841
1031
|
PaymentUI,
|
|
842
1032
|
createLoginUI,
|
|
843
|
-
createPaymentUI
|
|
1033
|
+
createPaymentUI,
|
|
1034
|
+
handleLoginCallbackIfPresent
|
|
844
1035
|
};
|
|
845
1036
|
//# sourceMappingURL=index.js.map
|