@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.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,70 @@ 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) {
|
|
232
|
+
const parentOrigin = typeof window !== "undefined" ? window.location.origin : void 0;
|
|
233
|
+
const callbackUrl = typeof window !== "undefined" && callbackState ? buildLoginCallbackUrl(callbackState, options?.callbackUrl) : void 0;
|
|
234
|
+
return {
|
|
235
|
+
autoClose: options?.autoClose ?? true,
|
|
236
|
+
callbackUrl,
|
|
237
|
+
callbackState,
|
|
238
|
+
origin: parentOrigin,
|
|
239
|
+
preferredChannel: params.preferredChannel
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function appendLoginLaunchHash(urlValue, payload) {
|
|
243
|
+
const hashParams = new URLSearchParams();
|
|
244
|
+
if (payload.autoClose === false) {
|
|
245
|
+
hashParams.set("ydAutoClose", "false");
|
|
246
|
+
}
|
|
247
|
+
if (payload.origin) {
|
|
248
|
+
hashParams.set("ydOrigin", payload.origin);
|
|
249
|
+
}
|
|
250
|
+
if (payload.callbackUrl) {
|
|
251
|
+
hashParams.set("ydCallbackUrl", payload.callbackUrl);
|
|
252
|
+
}
|
|
253
|
+
if (payload.preferredChannel) {
|
|
254
|
+
hashParams.set("ydPreferredChannel", payload.preferredChannel);
|
|
255
|
+
}
|
|
256
|
+
const hash = hashParams.toString();
|
|
257
|
+
if (!hash) {
|
|
258
|
+
return urlValue;
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const url = new URL(urlValue);
|
|
262
|
+
url.hash = hash;
|
|
263
|
+
return url.toString();
|
|
264
|
+
} catch (_error) {
|
|
265
|
+
return `${urlValue}#${hash}`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function buildLoginPopupName(payload) {
|
|
269
|
+
return `youidian-login:${JSON.stringify(payload)}`;
|
|
270
|
+
}
|
|
271
|
+
function buildLoginUrl(params, options, launchPayload) {
|
|
321
272
|
const { appId, baseUrl = "https://pay.imgto.link", loginUrl } = params;
|
|
322
273
|
const rawBase = (loginUrl || baseUrl).replace(/\/$/, "");
|
|
323
|
-
const parentOrigin =
|
|
274
|
+
const parentOrigin = launchPayload.origin;
|
|
324
275
|
let finalUrl;
|
|
325
276
|
try {
|
|
326
277
|
const url = new URL(rawBase);
|
|
@@ -344,24 +295,39 @@ function buildLoginUrl(params) {
|
|
|
344
295
|
if (params.preferredChannel) {
|
|
345
296
|
url.searchParams.set("preferredChannel", params.preferredChannel);
|
|
346
297
|
}
|
|
298
|
+
if (options?.autoClose === false) {
|
|
299
|
+
url.searchParams.set("autoClose", "false");
|
|
300
|
+
}
|
|
347
301
|
if (parentOrigin) {
|
|
348
302
|
url.searchParams.set("origin", parentOrigin);
|
|
349
303
|
}
|
|
350
|
-
|
|
304
|
+
if (launchPayload.callbackUrl) {
|
|
305
|
+
url.searchParams.set("callbackUrl", launchPayload.callbackUrl);
|
|
306
|
+
}
|
|
307
|
+
return appendLoginLaunchHash(url.toString(), launchPayload);
|
|
351
308
|
} catch (_error) {
|
|
352
309
|
const searchParams = new URLSearchParams();
|
|
353
310
|
if (params.preferredChannel) {
|
|
354
311
|
searchParams.set("preferredChannel", params.preferredChannel);
|
|
355
312
|
}
|
|
313
|
+
if (options?.autoClose === false) {
|
|
314
|
+
searchParams.set("autoClose", "false");
|
|
315
|
+
}
|
|
356
316
|
if (parentOrigin) {
|
|
357
317
|
searchParams.set("origin", parentOrigin);
|
|
358
318
|
}
|
|
319
|
+
if (launchPayload.callbackUrl) {
|
|
320
|
+
searchParams.set("callbackUrl", launchPayload.callbackUrl);
|
|
321
|
+
}
|
|
359
322
|
const query = searchParams.toString();
|
|
360
323
|
if (!query) {
|
|
361
|
-
return finalUrl;
|
|
324
|
+
return appendLoginLaunchHash(finalUrl, launchPayload);
|
|
362
325
|
}
|
|
363
326
|
const separator = finalUrl.includes("?") ? "&" : "?";
|
|
364
|
-
return
|
|
327
|
+
return appendLoginLaunchHash(
|
|
328
|
+
`${finalUrl}${separator}${query}`,
|
|
329
|
+
launchPayload
|
|
330
|
+
);
|
|
365
331
|
}
|
|
366
332
|
}
|
|
367
333
|
function extractLoginResult(data) {
|
|
@@ -379,6 +345,102 @@ function extractLoginResult(data) {
|
|
|
379
345
|
};
|
|
380
346
|
}
|
|
381
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
|
+
}
|
|
382
444
|
function logLoginDebug(message, details) {
|
|
383
445
|
if (typeof console === "undefined") return;
|
|
384
446
|
console.debug(LOGIN_UI_LOG_PREFIX, message, details || {});
|
|
@@ -394,14 +456,24 @@ function logLoginWarn(message, details) {
|
|
|
394
456
|
var LoginUI = class {
|
|
395
457
|
constructor() {
|
|
396
458
|
__publicField(this, "popup", null);
|
|
459
|
+
__publicField(this, "callbackChannel", null);
|
|
460
|
+
__publicField(this, "callbackStorageHandler", null);
|
|
397
461
|
__publicField(this, "messageHandler", null);
|
|
398
462
|
__publicField(this, "closeMonitor", null);
|
|
399
463
|
__publicField(this, "completed", false);
|
|
400
464
|
}
|
|
401
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
|
+
}
|
|
402
472
|
if (typeof window !== "undefined" && this.messageHandler) {
|
|
403
473
|
window.removeEventListener("message", this.messageHandler);
|
|
404
474
|
}
|
|
475
|
+
this.callbackChannel = null;
|
|
476
|
+
this.callbackStorageHandler = null;
|
|
405
477
|
if (typeof window !== "undefined" && this.closeMonitor) {
|
|
406
478
|
window.clearInterval(this.closeMonitor);
|
|
407
479
|
}
|
|
@@ -410,6 +482,81 @@ var LoginUI = class {
|
|
|
410
482
|
this.popup = null;
|
|
411
483
|
this.completed = false;
|
|
412
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
|
+
}
|
|
413
560
|
close() {
|
|
414
561
|
logLoginInfo("close() called", {
|
|
415
562
|
hasPopup: Boolean(this.popup),
|
|
@@ -423,7 +570,13 @@ var LoginUI = class {
|
|
|
423
570
|
openLogin(params, options) {
|
|
424
571
|
if (typeof window === "undefined") return;
|
|
425
572
|
if (this.popup && !this.popup.closed) return;
|
|
426
|
-
const
|
|
573
|
+
const callbackState = createLoginCallbackState();
|
|
574
|
+
const launchPayload = buildLoginLaunchPayload(
|
|
575
|
+
params,
|
|
576
|
+
options,
|
|
577
|
+
callbackState
|
|
578
|
+
);
|
|
579
|
+
const finalUrl = buildLoginUrl(params, options, launchPayload);
|
|
427
580
|
logLoginInfo("Opening hosted login popup", {
|
|
428
581
|
appId: params.appId,
|
|
429
582
|
loginUrl: finalUrl,
|
|
@@ -449,7 +602,8 @@ var LoginUI = class {
|
|
|
449
602
|
"resizable=yes",
|
|
450
603
|
"scrollbars=yes"
|
|
451
604
|
].join(",");
|
|
452
|
-
const
|
|
605
|
+
const popupName = buildLoginPopupName(launchPayload);
|
|
606
|
+
const popup = window.open(finalUrl, popupName, features);
|
|
453
607
|
if (!popup) {
|
|
454
608
|
logLoginWarn("Popup blocked by the browser");
|
|
455
609
|
options?.onError?.("Login popup was blocked");
|
|
@@ -457,6 +611,7 @@ var LoginUI = class {
|
|
|
457
611
|
}
|
|
458
612
|
this.popup = popup;
|
|
459
613
|
this.completed = false;
|
|
614
|
+
this.listenForLoginCallback(callbackState, options);
|
|
460
615
|
const allowedOrigin = options?.allowedOrigin;
|
|
461
616
|
const popupOrigin = getOrigin(finalUrl);
|
|
462
617
|
this.messageHandler = (event) => {
|
|
@@ -483,38 +638,7 @@ var LoginUI = class {
|
|
|
483
638
|
type: data.type,
|
|
484
639
|
eventOrigin: event.origin
|
|
485
640
|
});
|
|
486
|
-
|
|
487
|
-
case "LOGIN_SUCCESS":
|
|
488
|
-
this.completed = true;
|
|
489
|
-
logLoginInfo("Login succeeded", {
|
|
490
|
-
channel: data.channel || null,
|
|
491
|
-
userId: data.userId || null
|
|
492
|
-
});
|
|
493
|
-
options?.onSuccess?.(extractLoginResult(data));
|
|
494
|
-
break;
|
|
495
|
-
case "LOGIN_CANCELLED":
|
|
496
|
-
logLoginInfo("Login cancelled");
|
|
497
|
-
options?.onCancel?.();
|
|
498
|
-
break;
|
|
499
|
-
case "LOGIN_RESIZE":
|
|
500
|
-
break;
|
|
501
|
-
case "LOGIN_ERROR":
|
|
502
|
-
logLoginWarn("Login flow reported an error", {
|
|
503
|
-
message: data.message || data.error || null
|
|
504
|
-
});
|
|
505
|
-
options?.onError?.(data.message || data.error, data);
|
|
506
|
-
break;
|
|
507
|
-
case "LOGIN_CLOSE":
|
|
508
|
-
if (options?.autoClose === false) {
|
|
509
|
-
logLoginInfo("Login popup requested close; autoClose disabled, keeping popup open");
|
|
510
|
-
options?.onClose?.();
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
logLoginInfo("Login popup requested close; autoClose enabled");
|
|
514
|
-
this.close();
|
|
515
|
-
options?.onClose?.();
|
|
516
|
-
break;
|
|
517
|
-
}
|
|
641
|
+
this.handleLoginEvent(data, options);
|
|
518
642
|
};
|
|
519
643
|
window.addEventListener("message", this.messageHandler);
|
|
520
644
|
this.closeMonitor = window.setInterval(() => {
|
|
@@ -535,9 +659,119 @@ var LoginUI = class {
|
|
|
535
659
|
function createLoginUI() {
|
|
536
660
|
return new LoginUI();
|
|
537
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
|
+
}
|
|
538
772
|
|
|
539
773
|
// src/server.ts
|
|
540
|
-
import
|
|
774
|
+
import crypto2 from "crypto";
|
|
541
775
|
var PaymentClient = class {
|
|
542
776
|
// 用于生成 checkout URL
|
|
543
777
|
constructor(options) {
|
|
@@ -561,7 +795,7 @@ var PaymentClient = class {
|
|
|
561
795
|
*/
|
|
562
796
|
generateSignature(timestamp) {
|
|
563
797
|
const str = `${this.appId}${this.appSecret}${timestamp}`;
|
|
564
|
-
return
|
|
798
|
+
return crypto2.createHash("sha256").update(str).digest("hex");
|
|
565
799
|
}
|
|
566
800
|
/**
|
|
567
801
|
* Internal request helper for Gateway API
|
|
@@ -608,8 +842,8 @@ var PaymentClient = class {
|
|
|
608
842
|
decryptCallback(notification) {
|
|
609
843
|
try {
|
|
610
844
|
const { iv, encryptedData, authTag } = notification;
|
|
611
|
-
const key =
|
|
612
|
-
const decipher =
|
|
845
|
+
const key = crypto2.createHash("sha256").update(this.appSecret).digest();
|
|
846
|
+
const decipher = crypto2.createDecipheriv(
|
|
613
847
|
"aes-256-gcm",
|
|
614
848
|
key,
|
|
615
849
|
Buffer.from(iv, "hex")
|
|
@@ -791,6 +1025,7 @@ export {
|
|
|
791
1025
|
PaymentClient,
|
|
792
1026
|
PaymentUI,
|
|
793
1027
|
createLoginUI,
|
|
794
|
-
createPaymentUI
|
|
1028
|
+
createPaymentUI,
|
|
1029
|
+
handleLoginCallbackIfPresent
|
|
795
1030
|
};
|
|
796
1031
|
//# sourceMappingURL=index.js.map
|