@youidian/sdk 3.1.1 → 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 +339 -152
- 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 +341 -155
- 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 +230 -43
- 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 +228 -42
- 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.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,7 +570,13 @@ 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,
|
|
473
582
|
loginUrl: finalUrl,
|
|
@@ -493,9 +602,7 @@ var LoginUI = class {
|
|
|
493
602
|
"resizable=yes",
|
|
494
603
|
"scrollbars=yes"
|
|
495
604
|
].join(",");
|
|
496
|
-
const popupName = buildLoginPopupName(
|
|
497
|
-
buildLoginLaunchPayload(params, options)
|
|
498
|
-
);
|
|
605
|
+
const popupName = buildLoginPopupName(launchPayload);
|
|
499
606
|
const popup = window.open(finalUrl, popupName, features);
|
|
500
607
|
if (!popup) {
|
|
501
608
|
logLoginWarn("Popup blocked by the browser");
|
|
@@ -504,6 +611,7 @@ var LoginUI = class {
|
|
|
504
611
|
}
|
|
505
612
|
this.popup = popup;
|
|
506
613
|
this.completed = false;
|
|
614
|
+
this.listenForLoginCallback(callbackState, options);
|
|
507
615
|
const allowedOrigin = options?.allowedOrigin;
|
|
508
616
|
const popupOrigin = getOrigin(finalUrl);
|
|
509
617
|
this.messageHandler = (event) => {
|
|
@@ -530,40 +638,7 @@ var LoginUI = class {
|
|
|
530
638
|
type: data.type,
|
|
531
639
|
eventOrigin: event.origin
|
|
532
640
|
});
|
|
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
|
-
}
|
|
641
|
+
this.handleLoginEvent(data, options);
|
|
567
642
|
};
|
|
568
643
|
window.addEventListener("message", this.messageHandler);
|
|
569
644
|
this.closeMonitor = window.setInterval(() => {
|
|
@@ -584,9 +659,119 @@ var LoginUI = class {
|
|
|
584
659
|
function createLoginUI() {
|
|
585
660
|
return new LoginUI();
|
|
586
661
|
}
|
|
662
|
+
handleLoginCallbackIfPresent();
|
|
663
|
+
|
|
664
|
+
// src/client.ts
|
|
665
|
+
var PaymentUI = class extends HostedFrameModal {
|
|
666
|
+
/**
|
|
667
|
+
* Opens the payment checkout page in an iframe modal.
|
|
668
|
+
* @param urlOrParams - The checkout page URL or payment parameters
|
|
669
|
+
* @param options - UI options
|
|
670
|
+
*/
|
|
671
|
+
openPayment(urlOrParams, options) {
|
|
672
|
+
if (typeof document === "undefined") return;
|
|
673
|
+
if (this.modal) return;
|
|
674
|
+
let checkoutUrl;
|
|
675
|
+
if (typeof urlOrParams === "string") {
|
|
676
|
+
checkoutUrl = urlOrParams;
|
|
677
|
+
} else {
|
|
678
|
+
const {
|
|
679
|
+
appId,
|
|
680
|
+
productId,
|
|
681
|
+
priceId,
|
|
682
|
+
productCode,
|
|
683
|
+
userId,
|
|
684
|
+
checkoutUrl: checkoutUrlParam,
|
|
685
|
+
baseUrl = "https://pay.imgto.link"
|
|
686
|
+
} = urlOrParams;
|
|
687
|
+
const base = (checkoutUrlParam || baseUrl).replace(/\/$/, "");
|
|
688
|
+
if (productCode) {
|
|
689
|
+
checkoutUrl = `${base}/checkout/${appId}/code/${productCode}?userId=${encodeURIComponent(userId)}`;
|
|
690
|
+
} else if (productId && priceId) {
|
|
691
|
+
checkoutUrl = `${base}/checkout/${appId}/${productId}/${priceId}?userId=${encodeURIComponent(userId)}`;
|
|
692
|
+
} else {
|
|
693
|
+
throw new Error(
|
|
694
|
+
"Either productCode or both productId and priceId are required"
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const finalUrl = applyLocaleToUrl(checkoutUrl, options?.locale);
|
|
699
|
+
this.openHostedFrame(finalUrl, {
|
|
700
|
+
allowedOrigin: options?.allowedOrigin,
|
|
701
|
+
onCloseButton: () => options?.onCancel?.(),
|
|
702
|
+
onMessage: (data, container) => {
|
|
703
|
+
switch (data.type) {
|
|
704
|
+
case "PAYMENT_SUCCESS":
|
|
705
|
+
options?.onSuccess?.(data.orderId);
|
|
706
|
+
break;
|
|
707
|
+
case "PAYMENT_CANCELLED":
|
|
708
|
+
options?.onCancel?.(data.orderId);
|
|
709
|
+
break;
|
|
710
|
+
case "PAYMENT_RESIZE":
|
|
711
|
+
if (data.height) {
|
|
712
|
+
const maxHeight = window.innerHeight * 0.9;
|
|
713
|
+
const newHeight = Math.min(data.height, maxHeight);
|
|
714
|
+
container.style.height = `${newHeight}px`;
|
|
715
|
+
}
|
|
716
|
+
break;
|
|
717
|
+
case "PAYMENT_CLOSE":
|
|
718
|
+
this.close();
|
|
719
|
+
options?.onClose?.();
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Poll order status from integrator's API endpoint
|
|
727
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
728
|
+
* @param options - Polling options
|
|
729
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
730
|
+
*/
|
|
731
|
+
async pollOrderStatus(statusUrl, options) {
|
|
732
|
+
const interval = options?.interval || 3e3;
|
|
733
|
+
const timeout = options?.timeout || 3e5;
|
|
734
|
+
const startTime = Date.now();
|
|
735
|
+
return new Promise((resolve, reject) => {
|
|
736
|
+
const poll = async () => {
|
|
737
|
+
try {
|
|
738
|
+
const response = await fetch(statusUrl);
|
|
739
|
+
if (!response.ok) {
|
|
740
|
+
throw new Error(`Status check failed: ${response.status}`);
|
|
741
|
+
}
|
|
742
|
+
const status = await response.json();
|
|
743
|
+
options?.onStatusChange?.(status);
|
|
744
|
+
if (status.status === "PAID") {
|
|
745
|
+
resolve(status);
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
749
|
+
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
if (Date.now() - startTime > timeout) {
|
|
753
|
+
reject(new Error("Polling timeout"));
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
setTimeout(poll, interval);
|
|
757
|
+
} catch (error) {
|
|
758
|
+
if (Date.now() - startTime > timeout) {
|
|
759
|
+
reject(error);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
setTimeout(poll, interval);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
poll();
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
function createPaymentUI() {
|
|
770
|
+
return new PaymentUI();
|
|
771
|
+
}
|
|
587
772
|
|
|
588
773
|
// src/server.ts
|
|
589
|
-
import
|
|
774
|
+
import crypto2 from "crypto";
|
|
590
775
|
var PaymentClient = class {
|
|
591
776
|
// 用于生成 checkout URL
|
|
592
777
|
constructor(options) {
|
|
@@ -610,7 +795,7 @@ var PaymentClient = class {
|
|
|
610
795
|
*/
|
|
611
796
|
generateSignature(timestamp) {
|
|
612
797
|
const str = `${this.appId}${this.appSecret}${timestamp}`;
|
|
613
|
-
return
|
|
798
|
+
return crypto2.createHash("sha256").update(str).digest("hex");
|
|
614
799
|
}
|
|
615
800
|
/**
|
|
616
801
|
* Internal request helper for Gateway API
|
|
@@ -657,8 +842,8 @@ var PaymentClient = class {
|
|
|
657
842
|
decryptCallback(notification) {
|
|
658
843
|
try {
|
|
659
844
|
const { iv, encryptedData, authTag } = notification;
|
|
660
|
-
const key =
|
|
661
|
-
const decipher =
|
|
845
|
+
const key = crypto2.createHash("sha256").update(this.appSecret).digest();
|
|
846
|
+
const decipher = crypto2.createDecipheriv(
|
|
662
847
|
"aes-256-gcm",
|
|
663
848
|
key,
|
|
664
849
|
Buffer.from(iv, "hex")
|
|
@@ -840,6 +1025,7 @@ export {
|
|
|
840
1025
|
PaymentClient,
|
|
841
1026
|
PaymentUI,
|
|
842
1027
|
createLoginUI,
|
|
843
|
-
createPaymentUI
|
|
1028
|
+
createPaymentUI,
|
|
1029
|
+
handleLoginCallbackIfPresent
|
|
844
1030
|
};
|
|
845
1031
|
//# sourceMappingURL=index.js.map
|