@youidian/pay-sdk 1.0.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.d.mts +80 -0
- package/dist/client.d.ts +80 -0
- package/dist/client.js +209 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +185 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +351 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +314 -0
- package/dist/index.mjs.map +1 -0
- package/dist/server.d.mts +160 -0
- package/dist/server.d.ts +160 -0
- package/dist/server.js +168 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +135 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Youidian Payment SDK - Client Module
|
|
3
|
+
* 用于浏览器端集成,包含支付弹窗、状态轮询等功能
|
|
4
|
+
* 不依赖 Node.js crypto 模块
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Payment event types from checkout pages
|
|
8
|
+
*/
|
|
9
|
+
type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';
|
|
10
|
+
/**
|
|
11
|
+
* Payment event data from postMessage
|
|
12
|
+
*/
|
|
13
|
+
interface PaymentEventData {
|
|
14
|
+
type: PaymentEventType;
|
|
15
|
+
orderId?: string;
|
|
16
|
+
height?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Order status response
|
|
20
|
+
*/
|
|
21
|
+
interface OrderStatus {
|
|
22
|
+
orderId: string;
|
|
23
|
+
status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
|
|
24
|
+
paidAt?: string;
|
|
25
|
+
channelTransactionId?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Payment UI Options
|
|
29
|
+
*/
|
|
30
|
+
interface PaymentUIOptions {
|
|
31
|
+
locale?: string;
|
|
32
|
+
onSuccess?: (orderId?: string) => void;
|
|
33
|
+
onCancel?: (orderId?: string) => void;
|
|
34
|
+
onClose?: () => void;
|
|
35
|
+
/** Origin to validate postMessage (defaults to '*', set for security) */
|
|
36
|
+
allowedOrigin?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Poll Options
|
|
40
|
+
*/
|
|
41
|
+
interface PollOptions {
|
|
42
|
+
/** Polling interval in ms (default: 3000) */
|
|
43
|
+
interval?: number;
|
|
44
|
+
/** Timeout in ms (default: 300000 = 5 minutes) */
|
|
45
|
+
timeout?: number;
|
|
46
|
+
/** Callback on each status check */
|
|
47
|
+
onStatusChange?: (status: OrderStatus) => void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Client-side Payment UI Helper
|
|
51
|
+
* 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态
|
|
52
|
+
*/
|
|
53
|
+
declare class PaymentUI {
|
|
54
|
+
private iframe;
|
|
55
|
+
private modal;
|
|
56
|
+
private messageHandler;
|
|
57
|
+
/**
|
|
58
|
+
* Opens the payment checkout page in an iframe modal.
|
|
59
|
+
* @param checkoutUrl - The checkout page URL
|
|
60
|
+
* @param options - UI options
|
|
61
|
+
*/
|
|
62
|
+
openPayment(checkoutUrl: string, options?: PaymentUIOptions): void;
|
|
63
|
+
/**
|
|
64
|
+
* Close the payment modal
|
|
65
|
+
*/
|
|
66
|
+
close(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Poll order status from integrator's API endpoint
|
|
69
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
70
|
+
* @param options - Polling options
|
|
71
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
72
|
+
*/
|
|
73
|
+
pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convenience function to create a PaymentUI instance
|
|
77
|
+
*/
|
|
78
|
+
declare function createPaymentUI(): PaymentUI;
|
|
79
|
+
|
|
80
|
+
export { type OrderStatus, type PaymentEventData, type PaymentEventType, PaymentUI, type PaymentUIOptions, type PollOptions, createPaymentUI };
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Youidian Payment SDK - Client Module
|
|
3
|
+
* 用于浏览器端集成,包含支付弹窗、状态轮询等功能
|
|
4
|
+
* 不依赖 Node.js crypto 模块
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Payment event types from checkout pages
|
|
8
|
+
*/
|
|
9
|
+
type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';
|
|
10
|
+
/**
|
|
11
|
+
* Payment event data from postMessage
|
|
12
|
+
*/
|
|
13
|
+
interface PaymentEventData {
|
|
14
|
+
type: PaymentEventType;
|
|
15
|
+
orderId?: string;
|
|
16
|
+
height?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Order status response
|
|
20
|
+
*/
|
|
21
|
+
interface OrderStatus {
|
|
22
|
+
orderId: string;
|
|
23
|
+
status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';
|
|
24
|
+
paidAt?: string;
|
|
25
|
+
channelTransactionId?: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Payment UI Options
|
|
29
|
+
*/
|
|
30
|
+
interface PaymentUIOptions {
|
|
31
|
+
locale?: string;
|
|
32
|
+
onSuccess?: (orderId?: string) => void;
|
|
33
|
+
onCancel?: (orderId?: string) => void;
|
|
34
|
+
onClose?: () => void;
|
|
35
|
+
/** Origin to validate postMessage (defaults to '*', set for security) */
|
|
36
|
+
allowedOrigin?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Poll Options
|
|
40
|
+
*/
|
|
41
|
+
interface PollOptions {
|
|
42
|
+
/** Polling interval in ms (default: 3000) */
|
|
43
|
+
interval?: number;
|
|
44
|
+
/** Timeout in ms (default: 300000 = 5 minutes) */
|
|
45
|
+
timeout?: number;
|
|
46
|
+
/** Callback on each status check */
|
|
47
|
+
onStatusChange?: (status: OrderStatus) => void;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Client-side Payment UI Helper
|
|
51
|
+
* 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态
|
|
52
|
+
*/
|
|
53
|
+
declare class PaymentUI {
|
|
54
|
+
private iframe;
|
|
55
|
+
private modal;
|
|
56
|
+
private messageHandler;
|
|
57
|
+
/**
|
|
58
|
+
* Opens the payment checkout page in an iframe modal.
|
|
59
|
+
* @param checkoutUrl - The checkout page URL
|
|
60
|
+
* @param options - UI options
|
|
61
|
+
*/
|
|
62
|
+
openPayment(checkoutUrl: string, options?: PaymentUIOptions): void;
|
|
63
|
+
/**
|
|
64
|
+
* Close the payment modal
|
|
65
|
+
*/
|
|
66
|
+
close(): void;
|
|
67
|
+
/**
|
|
68
|
+
* Poll order status from integrator's API endpoint
|
|
69
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
70
|
+
* @param options - Polling options
|
|
71
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
72
|
+
*/
|
|
73
|
+
pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convenience function to create a PaymentUI instance
|
|
77
|
+
*/
|
|
78
|
+
declare function createPaymentUI(): PaymentUI;
|
|
79
|
+
|
|
80
|
+
export { type OrderStatus, type PaymentEventData, type PaymentEventType, PaymentUI, type PaymentUIOptions, type PollOptions, createPaymentUI };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
21
|
+
|
|
22
|
+
// src/client.ts
|
|
23
|
+
var client_exports = {};
|
|
24
|
+
__export(client_exports, {
|
|
25
|
+
PaymentUI: () => PaymentUI,
|
|
26
|
+
createPaymentUI: () => createPaymentUI
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(client_exports);
|
|
29
|
+
var PaymentUI = class {
|
|
30
|
+
constructor() {
|
|
31
|
+
__publicField(this, "iframe", null);
|
|
32
|
+
__publicField(this, "modal", null);
|
|
33
|
+
__publicField(this, "messageHandler", null);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Opens the payment checkout page in an iframe modal.
|
|
37
|
+
* @param checkoutUrl - The checkout page URL
|
|
38
|
+
* @param options - UI options
|
|
39
|
+
*/
|
|
40
|
+
openPayment(checkoutUrl, options) {
|
|
41
|
+
if (typeof document === "undefined") return;
|
|
42
|
+
if (this.modal) return;
|
|
43
|
+
let finalUrl = checkoutUrl;
|
|
44
|
+
if (options?.locale) {
|
|
45
|
+
try {
|
|
46
|
+
const url = new URL(checkoutUrl);
|
|
47
|
+
if (!url.pathname.startsWith(`/${options.locale}/`)) {
|
|
48
|
+
url.pathname = `/${options.locale}${url.pathname}`;
|
|
49
|
+
finalUrl = url.toString();
|
|
50
|
+
}
|
|
51
|
+
} catch (_e) {
|
|
52
|
+
if (!checkoutUrl.startsWith(`/${options.locale}/`)) {
|
|
53
|
+
finalUrl = `/${options.locale}${checkoutUrl.startsWith("/") ? "" : "/"}${checkoutUrl}`;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
this.modal = document.createElement("div");
|
|
58
|
+
Object.assign(this.modal.style, {
|
|
59
|
+
position: "fixed",
|
|
60
|
+
top: "0",
|
|
61
|
+
left: "0",
|
|
62
|
+
width: "100%",
|
|
63
|
+
height: "100%",
|
|
64
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
65
|
+
display: "flex",
|
|
66
|
+
alignItems: "center",
|
|
67
|
+
justifyContent: "center",
|
|
68
|
+
zIndex: "9999",
|
|
69
|
+
transition: "opacity 0.3s ease"
|
|
70
|
+
});
|
|
71
|
+
const container = document.createElement("div");
|
|
72
|
+
Object.assign(container.style, {
|
|
73
|
+
width: "450px",
|
|
74
|
+
height: "min(600px, 90vh)",
|
|
75
|
+
backgroundColor: "#fff",
|
|
76
|
+
borderRadius: "20px",
|
|
77
|
+
overflow: "hidden",
|
|
78
|
+
position: "relative",
|
|
79
|
+
boxShadow: "0 25px 50px -12px rgba(0,0,0,0.25)"
|
|
80
|
+
});
|
|
81
|
+
const closeBtn = document.createElement("button");
|
|
82
|
+
closeBtn.innerHTML = "\xD7";
|
|
83
|
+
Object.assign(closeBtn.style, {
|
|
84
|
+
position: "absolute",
|
|
85
|
+
right: "15px",
|
|
86
|
+
top: "10px",
|
|
87
|
+
fontSize: "24px",
|
|
88
|
+
border: "none",
|
|
89
|
+
background: "none",
|
|
90
|
+
cursor: "pointer",
|
|
91
|
+
color: "#999",
|
|
92
|
+
zIndex: "1"
|
|
93
|
+
});
|
|
94
|
+
closeBtn.onclick = () => {
|
|
95
|
+
this.close();
|
|
96
|
+
options?.onCancel?.();
|
|
97
|
+
};
|
|
98
|
+
this.iframe = document.createElement("iframe");
|
|
99
|
+
this.iframe.src = finalUrl;
|
|
100
|
+
Object.assign(this.iframe.style, {
|
|
101
|
+
width: "100%",
|
|
102
|
+
height: "100%",
|
|
103
|
+
border: "none"
|
|
104
|
+
});
|
|
105
|
+
container.appendChild(closeBtn);
|
|
106
|
+
container.appendChild(this.iframe);
|
|
107
|
+
this.modal.appendChild(container);
|
|
108
|
+
document.body.appendChild(this.modal);
|
|
109
|
+
this.messageHandler = (event) => {
|
|
110
|
+
if (options?.allowedOrigin && options.allowedOrigin !== "*") {
|
|
111
|
+
if (event.origin !== options.allowedOrigin) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const data = event.data;
|
|
116
|
+
if (!data || typeof data !== "object" || !data.type) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
switch (data.type) {
|
|
120
|
+
case "PAYMENT_SUCCESS":
|
|
121
|
+
options?.onSuccess?.(data.orderId);
|
|
122
|
+
setTimeout(() => this.close(), 2e3);
|
|
123
|
+
break;
|
|
124
|
+
case "PAYMENT_CANCELLED":
|
|
125
|
+
options?.onCancel?.(data.orderId);
|
|
126
|
+
break;
|
|
127
|
+
case "PAYMENT_RESIZE":
|
|
128
|
+
if (data.height && container) {
|
|
129
|
+
const maxHeight = window.innerHeight * 0.9;
|
|
130
|
+
const newHeight = Math.min(data.height, maxHeight);
|
|
131
|
+
container.style.height = `${newHeight}px`;
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
case "PAYMENT_CLOSE":
|
|
135
|
+
this.close();
|
|
136
|
+
options?.onClose?.();
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
window.addEventListener("message", this.messageHandler);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Close the payment modal
|
|
144
|
+
*/
|
|
145
|
+
close() {
|
|
146
|
+
if (typeof window === "undefined") return;
|
|
147
|
+
if (this.messageHandler) {
|
|
148
|
+
window.removeEventListener("message", this.messageHandler);
|
|
149
|
+
this.messageHandler = null;
|
|
150
|
+
}
|
|
151
|
+
if (this.modal && this.modal.parentNode) {
|
|
152
|
+
this.modal.parentNode.removeChild(this.modal);
|
|
153
|
+
}
|
|
154
|
+
this.modal = null;
|
|
155
|
+
this.iframe = null;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Poll order status from integrator's API endpoint
|
|
159
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
160
|
+
* @param options - Polling options
|
|
161
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
162
|
+
*/
|
|
163
|
+
async pollOrderStatus(statusUrl, options) {
|
|
164
|
+
const interval = options?.interval || 3e3;
|
|
165
|
+
const timeout = options?.timeout || 3e5;
|
|
166
|
+
const startTime = Date.now();
|
|
167
|
+
return new Promise((resolve, reject) => {
|
|
168
|
+
const poll = async () => {
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(statusUrl);
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`Status check failed: ${response.status}`);
|
|
173
|
+
}
|
|
174
|
+
const status = await response.json();
|
|
175
|
+
options?.onStatusChange?.(status);
|
|
176
|
+
if (status.status === "PAID") {
|
|
177
|
+
resolve(status);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
181
|
+
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (Date.now() - startTime > timeout) {
|
|
185
|
+
reject(new Error("Polling timeout"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
setTimeout(poll, interval);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (Date.now() - startTime > timeout) {
|
|
191
|
+
reject(error);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
setTimeout(poll, interval);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
poll();
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
function createPaymentUI() {
|
|
202
|
+
return new PaymentUI();
|
|
203
|
+
}
|
|
204
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
205
|
+
0 && (module.exports = {
|
|
206
|
+
PaymentUI,
|
|
207
|
+
createPaymentUI
|
|
208
|
+
});
|
|
209
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param checkoutUrl - The checkout page URL\n * @param options - UI options\n */\n openPayment(checkoutUrl: string, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n if (!url.pathname.startsWith(`/${options.locale}/`)) {\n url.pathname = `/${options.locale}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n if (!checkoutUrl.startsWith(`/${options.locale}/`)) {\n finalUrl = `/${options.locale}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
var PaymentUI = class {
|
|
7
|
+
constructor() {
|
|
8
|
+
__publicField(this, "iframe", null);
|
|
9
|
+
__publicField(this, "modal", null);
|
|
10
|
+
__publicField(this, "messageHandler", null);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Opens the payment checkout page in an iframe modal.
|
|
14
|
+
* @param checkoutUrl - The checkout page URL
|
|
15
|
+
* @param options - UI options
|
|
16
|
+
*/
|
|
17
|
+
openPayment(checkoutUrl, options) {
|
|
18
|
+
if (typeof document === "undefined") return;
|
|
19
|
+
if (this.modal) return;
|
|
20
|
+
let finalUrl = checkoutUrl;
|
|
21
|
+
if (options?.locale) {
|
|
22
|
+
try {
|
|
23
|
+
const url = new URL(checkoutUrl);
|
|
24
|
+
if (!url.pathname.startsWith(`/${options.locale}/`)) {
|
|
25
|
+
url.pathname = `/${options.locale}${url.pathname}`;
|
|
26
|
+
finalUrl = url.toString();
|
|
27
|
+
}
|
|
28
|
+
} catch (_e) {
|
|
29
|
+
if (!checkoutUrl.startsWith(`/${options.locale}/`)) {
|
|
30
|
+
finalUrl = `/${options.locale}${checkoutUrl.startsWith("/") ? "" : "/"}${checkoutUrl}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
this.modal = document.createElement("div");
|
|
35
|
+
Object.assign(this.modal.style, {
|
|
36
|
+
position: "fixed",
|
|
37
|
+
top: "0",
|
|
38
|
+
left: "0",
|
|
39
|
+
width: "100%",
|
|
40
|
+
height: "100%",
|
|
41
|
+
backgroundColor: "rgba(0,0,0,0.5)",
|
|
42
|
+
display: "flex",
|
|
43
|
+
alignItems: "center",
|
|
44
|
+
justifyContent: "center",
|
|
45
|
+
zIndex: "9999",
|
|
46
|
+
transition: "opacity 0.3s ease"
|
|
47
|
+
});
|
|
48
|
+
const container = document.createElement("div");
|
|
49
|
+
Object.assign(container.style, {
|
|
50
|
+
width: "450px",
|
|
51
|
+
height: "min(600px, 90vh)",
|
|
52
|
+
backgroundColor: "#fff",
|
|
53
|
+
borderRadius: "20px",
|
|
54
|
+
overflow: "hidden",
|
|
55
|
+
position: "relative",
|
|
56
|
+
boxShadow: "0 25px 50px -12px rgba(0,0,0,0.25)"
|
|
57
|
+
});
|
|
58
|
+
const closeBtn = document.createElement("button");
|
|
59
|
+
closeBtn.innerHTML = "\xD7";
|
|
60
|
+
Object.assign(closeBtn.style, {
|
|
61
|
+
position: "absolute",
|
|
62
|
+
right: "15px",
|
|
63
|
+
top: "10px",
|
|
64
|
+
fontSize: "24px",
|
|
65
|
+
border: "none",
|
|
66
|
+
background: "none",
|
|
67
|
+
cursor: "pointer",
|
|
68
|
+
color: "#999",
|
|
69
|
+
zIndex: "1"
|
|
70
|
+
});
|
|
71
|
+
closeBtn.onclick = () => {
|
|
72
|
+
this.close();
|
|
73
|
+
options?.onCancel?.();
|
|
74
|
+
};
|
|
75
|
+
this.iframe = document.createElement("iframe");
|
|
76
|
+
this.iframe.src = finalUrl;
|
|
77
|
+
Object.assign(this.iframe.style, {
|
|
78
|
+
width: "100%",
|
|
79
|
+
height: "100%",
|
|
80
|
+
border: "none"
|
|
81
|
+
});
|
|
82
|
+
container.appendChild(closeBtn);
|
|
83
|
+
container.appendChild(this.iframe);
|
|
84
|
+
this.modal.appendChild(container);
|
|
85
|
+
document.body.appendChild(this.modal);
|
|
86
|
+
this.messageHandler = (event) => {
|
|
87
|
+
if (options?.allowedOrigin && options.allowedOrigin !== "*") {
|
|
88
|
+
if (event.origin !== options.allowedOrigin) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const data = event.data;
|
|
93
|
+
if (!data || typeof data !== "object" || !data.type) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
switch (data.type) {
|
|
97
|
+
case "PAYMENT_SUCCESS":
|
|
98
|
+
options?.onSuccess?.(data.orderId);
|
|
99
|
+
setTimeout(() => this.close(), 2e3);
|
|
100
|
+
break;
|
|
101
|
+
case "PAYMENT_CANCELLED":
|
|
102
|
+
options?.onCancel?.(data.orderId);
|
|
103
|
+
break;
|
|
104
|
+
case "PAYMENT_RESIZE":
|
|
105
|
+
if (data.height && container) {
|
|
106
|
+
const maxHeight = window.innerHeight * 0.9;
|
|
107
|
+
const newHeight = Math.min(data.height, maxHeight);
|
|
108
|
+
container.style.height = `${newHeight}px`;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case "PAYMENT_CLOSE":
|
|
112
|
+
this.close();
|
|
113
|
+
options?.onClose?.();
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
window.addEventListener("message", this.messageHandler);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Close the payment modal
|
|
121
|
+
*/
|
|
122
|
+
close() {
|
|
123
|
+
if (typeof window === "undefined") return;
|
|
124
|
+
if (this.messageHandler) {
|
|
125
|
+
window.removeEventListener("message", this.messageHandler);
|
|
126
|
+
this.messageHandler = null;
|
|
127
|
+
}
|
|
128
|
+
if (this.modal && this.modal.parentNode) {
|
|
129
|
+
this.modal.parentNode.removeChild(this.modal);
|
|
130
|
+
}
|
|
131
|
+
this.modal = null;
|
|
132
|
+
this.iframe = null;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Poll order status from integrator's API endpoint
|
|
136
|
+
* @param statusUrl - The integrator's API endpoint to check order status
|
|
137
|
+
* @param options - Polling options
|
|
138
|
+
* @returns Promise that resolves when order is paid or rejects on timeout/failure
|
|
139
|
+
*/
|
|
140
|
+
async pollOrderStatus(statusUrl, options) {
|
|
141
|
+
const interval = options?.interval || 3e3;
|
|
142
|
+
const timeout = options?.timeout || 3e5;
|
|
143
|
+
const startTime = Date.now();
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const poll = async () => {
|
|
146
|
+
try {
|
|
147
|
+
const response = await fetch(statusUrl);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
throw new Error(`Status check failed: ${response.status}`);
|
|
150
|
+
}
|
|
151
|
+
const status = await response.json();
|
|
152
|
+
options?.onStatusChange?.(status);
|
|
153
|
+
if (status.status === "PAID") {
|
|
154
|
+
resolve(status);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
if (status.status === "CANCELLED" || status.status === "FAILED") {
|
|
158
|
+
reject(new Error(`Order ${status.status.toLowerCase()}`));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (Date.now() - startTime > timeout) {
|
|
162
|
+
reject(new Error("Polling timeout"));
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
setTimeout(poll, interval);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (Date.now() - startTime > timeout) {
|
|
168
|
+
reject(error);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
setTimeout(poll, interval);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
poll();
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
function createPaymentUI() {
|
|
179
|
+
return new PaymentUI();
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
PaymentUI,
|
|
183
|
+
createPaymentUI
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["/**\n * Youidian Payment SDK - Client Module\n * 用于浏览器端集成,包含支付弹窗、状态轮询等功能\n * 不依赖 Node.js crypto 模块\n */\n\n/**\n * Payment event types from checkout pages\n */\nexport type PaymentEventType = 'PAYMENT_SUCCESS' | 'PAYMENT_CANCELLED' | 'PAYMENT_CLOSE' | 'PAYMENT_RESIZE';\n\n/**\n * Payment event data from postMessage\n */\nexport interface PaymentEventData {\n type: PaymentEventType;\n orderId?: string;\n height?: number;\n}\n\n/**\n * Order status response\n */\nexport interface OrderStatus {\n orderId: string;\n status: 'PENDING' | 'PAID' | 'CANCELLED' | 'REFUNDED' | 'FAILED';\n paidAt?: string;\n channelTransactionId?: string;\n}\n\n/**\n * Payment UI Options\n */\nexport interface PaymentUIOptions {\n locale?: string;\n onSuccess?: (orderId?: string) => void;\n onCancel?: (orderId?: string) => void;\n onClose?: () => void;\n /** Origin to validate postMessage (defaults to '*', set for security) */\n allowedOrigin?: string;\n}\n\n/**\n * Poll Options\n */\nexport interface PollOptions {\n /** Polling interval in ms (default: 3000) */\n interval?: number;\n /** Timeout in ms (default: 300000 = 5 minutes) */\n timeout?: number;\n /** Callback on each status check */\n onStatusChange?: (status: OrderStatus) => void;\n}\n\n/**\n * Client-side Payment UI Helper\n * 浏览器端支付 UI 辅助类,用于弹出支付窗口和轮询订单状态\n */\nexport class PaymentUI {\n private iframe: HTMLIFrameElement | null = null;\n private modal: HTMLDivElement | null = null;\n private messageHandler: ((event: MessageEvent) => void) | null = null;\n\n /**\n * Opens the payment checkout page in an iframe modal.\n * @param checkoutUrl - The checkout page URL\n * @param options - UI options\n */\n openPayment(checkoutUrl: string, options?: PaymentUIOptions) {\n if (typeof document === 'undefined') return; // Server-side guard\n if (this.modal) return; // Prevent multiple modals\n\n let finalUrl = checkoutUrl;\n if (options?.locale) {\n try {\n const url = new URL(checkoutUrl);\n if (!url.pathname.startsWith(`/${options.locale}/`)) {\n url.pathname = `/${options.locale}${url.pathname}`;\n finalUrl = url.toString();\n }\n } catch (_e) {\n // Fallback if URL is relative or invalid\n if (!checkoutUrl.startsWith(`/${options.locale}/`)) {\n finalUrl = `/${options.locale}${checkoutUrl.startsWith('/') ? '' : '/'}${checkoutUrl}`;\n }\n }\n }\n\n // Create Modal Overlay\n this.modal = document.createElement('div');\n Object.assign(this.modal.style, {\n position: 'fixed',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n backgroundColor: 'rgba(0,0,0,0.5)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: '9999',\n transition: 'opacity 0.3s ease'\n });\n\n // Create Container\n const container = document.createElement('div');\n Object.assign(container.style, {\n width: '450px',\n height: 'min(600px, 90vh)',\n backgroundColor: '#fff',\n borderRadius: '20px',\n overflow: 'hidden',\n position: 'relative',\n boxShadow: '0 25px 50px -12px rgba(0,0,0,0.25)'\n });\n\n // Create Close Button\n const closeBtn = document.createElement('button');\n closeBtn.innerHTML = '×';\n Object.assign(closeBtn.style, {\n position: 'absolute',\n right: '15px',\n top: '10px',\n fontSize: '24px',\n border: 'none',\n background: 'none',\n cursor: 'pointer',\n color: '#999',\n zIndex: '1'\n });\n closeBtn.onclick = () => {\n this.close();\n options?.onCancel?.();\n };\n\n // Create Iframe\n this.iframe = document.createElement('iframe');\n this.iframe.src = finalUrl;\n Object.assign(this.iframe.style, {\n width: '100%',\n height: '100%',\n border: 'none'\n });\n\n container.appendChild(closeBtn);\n container.appendChild(this.iframe);\n this.modal.appendChild(container);\n document.body.appendChild(this.modal);\n\n // Listen for messages from checkout page\n this.messageHandler = (event: MessageEvent) => {\n // Validate origin if specified\n if (options?.allowedOrigin && options.allowedOrigin !== '*') {\n if (event.origin !== options.allowedOrigin) {\n return;\n }\n }\n\n const data = event.data as PaymentEventData;\n\n if (!data || typeof data !== 'object' || !data.type) {\n return;\n }\n\n switch (data.type) {\n case 'PAYMENT_SUCCESS':\n options?.onSuccess?.(data.orderId);\n // Auto-close after success callback\n setTimeout(() => this.close(), 2000);\n break;\n case 'PAYMENT_CANCELLED':\n options?.onCancel?.(data.orderId);\n break;\n case 'PAYMENT_RESIZE':\n if (data.height && container) {\n // Limit max height to 90% of viewport\n const maxHeight = window.innerHeight * 0.9;\n const newHeight = Math.min(data.height, maxHeight);\n container.style.height = `${newHeight}px`;\n }\n break;\n case 'PAYMENT_CLOSE':\n this.close();\n options?.onClose?.();\n break;\n }\n };\n window.addEventListener('message', this.messageHandler);\n }\n\n /**\n * Close the payment modal\n */\n close() {\n if (typeof window === 'undefined') return;\n\n if (this.messageHandler) {\n window.removeEventListener('message', this.messageHandler);\n this.messageHandler = null;\n }\n if (this.modal && this.modal.parentNode) {\n this.modal.parentNode.removeChild(this.modal);\n }\n this.modal = null;\n this.iframe = null;\n }\n\n /**\n * Poll order status from integrator's API endpoint\n * @param statusUrl - The integrator's API endpoint to check order status\n * @param options - Polling options\n * @returns Promise that resolves when order is paid or rejects on timeout/failure\n */\n async pollOrderStatus(statusUrl: string, options?: PollOptions): Promise<OrderStatus> {\n const interval = options?.interval || 3000;\n const timeout = options?.timeout || 300000;\n const startTime = Date.now();\n\n return new Promise((resolve, reject) => {\n const poll = async () => {\n try {\n const response = await fetch(statusUrl);\n if (!response.ok) {\n throw new Error(`Status check failed: ${response.status}`);\n }\n\n const status: OrderStatus = await response.json();\n options?.onStatusChange?.(status);\n\n if (status.status === 'PAID') {\n resolve(status);\n return;\n }\n\n if (status.status === 'CANCELLED' || status.status === 'FAILED') {\n reject(new Error(`Order ${status.status.toLowerCase()}`));\n return;\n }\n\n // Check timeout\n if (Date.now() - startTime > timeout) {\n reject(new Error('Polling timeout'));\n return;\n }\n\n // Continue polling\n setTimeout(poll, interval);\n } catch (error) {\n // On network error, continue polling unless timeout\n if (Date.now() - startTime > timeout) {\n reject(error);\n return;\n }\n setTimeout(poll, interval);\n }\n };\n\n poll();\n });\n }\n}\n\n/**\n * Convenience function to create a PaymentUI instance\n */\nexport function createPaymentUI(): PaymentUI {\n return new PaymentUI();\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { OrderStatus as ClientOrderStatus, PaymentEventData, PaymentEventType, PaymentUI, PaymentUIOptions, PollOptions, createPaymentUI } from './client.mjs';
|
|
2
|
+
export { CreateOrderParams, CreateOrderResponse, OrderStatus, PaymentCallbackData, PaymentClient, PaymentClientOptions, PaymentNotification, Product, ProductEntitlements, ProductPrice } from './server.mjs';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { OrderStatus as ClientOrderStatus, PaymentEventData, PaymentEventType, PaymentUI, PaymentUIOptions, PollOptions, createPaymentUI } from './client.js';
|
|
2
|
+
export { CreateOrderParams, CreateOrderResponse, OrderStatus, PaymentCallbackData, PaymentClient, PaymentClientOptions, PaymentNotification, Product, ProductEntitlements, ProductPrice } from './server.js';
|