@seaverse/payment-sdk 0.8.0 → 0.8.2
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/README.md +229 -3
- package/dist/index.browser.js +1452 -1251
- package/dist/index.browser.js.map +1 -1
- package/dist/index.cjs +1452 -1250
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +275 -157
- package/dist/index.js +1452 -1251
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.browser.js
CHANGED
|
@@ -984,11 +984,33 @@ function centsToDollars(cents) {
|
|
|
984
984
|
function formatPrice(amount, currency = 'USD') {
|
|
985
985
|
const symbols = {
|
|
986
986
|
USD: '$',
|
|
987
|
-
CNY: '¥',
|
|
988
987
|
EUR: '€',
|
|
989
988
|
GBP: '£',
|
|
990
989
|
JPY: '¥',
|
|
990
|
+
CNY: '¥',
|
|
991
991
|
KRW: '₩',
|
|
992
|
+
AUD: 'A$',
|
|
993
|
+
CAD: 'C$',
|
|
994
|
+
CHF: 'CHF',
|
|
995
|
+
HKD: 'HK$',
|
|
996
|
+
SGD: 'S$',
|
|
997
|
+
INR: '₹',
|
|
998
|
+
RUB: '₽',
|
|
999
|
+
BRL: 'R$',
|
|
1000
|
+
MXN: 'MX$',
|
|
1001
|
+
ZAR: 'R',
|
|
1002
|
+
SEK: 'kr',
|
|
1003
|
+
NOK: 'kr',
|
|
1004
|
+
DKK: 'kr',
|
|
1005
|
+
PLN: 'zł',
|
|
1006
|
+
THB: '฿',
|
|
1007
|
+
IDR: 'Rp',
|
|
1008
|
+
MYR: 'RM',
|
|
1009
|
+
PHP: '₱',
|
|
1010
|
+
VND: '₫',
|
|
1011
|
+
TRY: '₺',
|
|
1012
|
+
AED: 'د.إ',
|
|
1013
|
+
SAR: 'SR',
|
|
992
1014
|
};
|
|
993
1015
|
const symbol = symbols[currency] || currency + ' ';
|
|
994
1016
|
return `${symbol}${amount.toFixed(2)}`;
|
|
@@ -2141,222 +2163,771 @@ class PaymentModal {
|
|
|
2141
2163
|
}
|
|
2142
2164
|
|
|
2143
2165
|
/**
|
|
2144
|
-
*
|
|
2145
|
-
*
|
|
2166
|
+
* RetentionModal - 支付挽留弹框
|
|
2167
|
+
* 当用户尝试取消支付时弹出,提供优惠信息以挽留用户
|
|
2168
|
+
* 参考图片中的深色卡片设计风格
|
|
2146
2169
|
*/
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
this.
|
|
2153
|
-
this.
|
|
2154
|
-
this.
|
|
2155
|
-
this.
|
|
2156
|
-
|
|
2157
|
-
this.
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
},
|
|
2166
|
-
...options.modalOptions,
|
|
2167
|
-
});
|
|
2168
|
-
console.log('[DropinPaymentModal] Created:', {
|
|
2169
|
-
orderId: this.orderId,
|
|
2170
|
-
paymentMethod: this.paymentMethod.payment_method_name,
|
|
2171
|
-
});
|
|
2170
|
+
/**
|
|
2171
|
+
* 支付挽留弹框类
|
|
2172
|
+
*/
|
|
2173
|
+
class RetentionModal {
|
|
2174
|
+
constructor(options) {
|
|
2175
|
+
this.overlay = null;
|
|
2176
|
+
this.modal = null;
|
|
2177
|
+
this.isExiting = false;
|
|
2178
|
+
this.scrollY = 0;
|
|
2179
|
+
this.boundHandleEscKey = null;
|
|
2180
|
+
this.countdownInterval = null;
|
|
2181
|
+
this.remainingSeconds = 60;
|
|
2182
|
+
this.options = {
|
|
2183
|
+
language: 'en',
|
|
2184
|
+
bonusAmount: 0,
|
|
2185
|
+
onClose: () => { },
|
|
2186
|
+
...options,
|
|
2187
|
+
};
|
|
2172
2188
|
}
|
|
2173
2189
|
/**
|
|
2174
|
-
*
|
|
2190
|
+
* 打开弹框
|
|
2175
2191
|
*/
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
throw new Error('Modal content container not found');
|
|
2191
|
-
}
|
|
2192
|
-
contentContainer.appendChild(this.containerElement);
|
|
2193
|
-
// 验证订单信息(关键:确保后端订单已创建)
|
|
2194
|
-
try {
|
|
2195
|
-
console.log('[DropinPaymentModal] Validating order info...');
|
|
2196
|
-
const response = await this.paymentInstance.getOrderInfo();
|
|
2197
|
-
if (response.err || !response.data?.order_info) {
|
|
2198
|
-
throw new Error(response.message || 'Order information not found');
|
|
2199
|
-
}
|
|
2200
|
-
const orderInfo = response.data.order_info;
|
|
2201
|
-
if (!orderInfo.sys_order_id) {
|
|
2202
|
-
throw new Error('Order ID (sys_order_id) is missing');
|
|
2203
|
-
}
|
|
2204
|
-
console.log('[DropinPaymentModal] Order validated:', {
|
|
2205
|
-
orderInfo,
|
|
2206
|
-
orderId: orderInfo.sys_order_id,
|
|
2207
|
-
status: orderInfo.order_status,
|
|
2208
|
-
});
|
|
2209
|
-
}
|
|
2210
|
-
catch (error) {
|
|
2211
|
-
console.error('[DropinPaymentModal] Order validation failed:', error);
|
|
2212
|
-
this.close();
|
|
2213
|
-
throw new Error(`Order validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2214
|
-
}
|
|
2215
|
-
// 创建并渲染 Dropin 支付组件
|
|
2216
|
-
try {
|
|
2217
|
-
this.dropinPayment = new DropinPaymentComponent({
|
|
2218
|
-
paymentInstance: this.paymentInstance,
|
|
2219
|
-
orderId: this.orderId,
|
|
2220
|
-
accountToken: this.accountToken,
|
|
2221
|
-
paymentMethod: this.paymentMethod,
|
|
2222
|
-
containerId: '#dropin-payment-container',
|
|
2223
|
-
onSubmit: (payload) => {
|
|
2224
|
-
console.log('[DropinPaymentModal] onSubmit:', payload);
|
|
2225
|
-
this.options.onSubmit?.(payload);
|
|
2226
|
-
},
|
|
2227
|
-
onError: (payload, error) => {
|
|
2228
|
-
console.error('[DropinPaymentModal] onError:', error);
|
|
2229
|
-
this.options.onError?.(payload, error);
|
|
2230
|
-
},
|
|
2231
|
-
onCreateOrder: (payload) => {
|
|
2232
|
-
console.log('[DropinPaymentModal] onCreateOrder:', payload);
|
|
2233
|
-
this.options.onCreateOrder?.(payload);
|
|
2234
|
-
},
|
|
2235
|
-
onCompleted: (payload) => {
|
|
2236
|
-
console.log('[DropinPaymentModal] onCompleted:', payload);
|
|
2237
|
-
this.options.onCompleted?.(payload);
|
|
2238
|
-
// 支付成功后自动关闭弹框(可选)
|
|
2239
|
-
setTimeout(() => this.close(), 2000);
|
|
2240
|
-
},
|
|
2241
|
-
onFailed: (payload) => {
|
|
2242
|
-
console.error('[DropinPaymentModal] onFailed:', payload);
|
|
2243
|
-
this.options.onFailed?.(payload);
|
|
2244
|
-
},
|
|
2245
|
-
onLoading: (loading) => {
|
|
2246
|
-
this.options.onLoading?.(loading);
|
|
2247
|
-
// 可以在这里显示加载状态
|
|
2248
|
-
if (loading) {
|
|
2249
|
-
this.showLoading();
|
|
2250
|
-
}
|
|
2251
|
-
else {
|
|
2252
|
-
this.hideLoading();
|
|
2253
|
-
}
|
|
2254
|
-
},
|
|
2255
|
-
});
|
|
2256
|
-
await this.dropinPayment.render();
|
|
2257
|
-
console.log('[DropinPaymentModal] Dropin component rendered');
|
|
2258
|
-
}
|
|
2259
|
-
catch (error) {
|
|
2260
|
-
console.error('[DropinPaymentModal] Failed to render dropin:', error);
|
|
2261
|
-
this.close();
|
|
2262
|
-
throw error;
|
|
2263
|
-
}
|
|
2192
|
+
open() {
|
|
2193
|
+
if (this.overlay)
|
|
2194
|
+
return; // 防止重复打开
|
|
2195
|
+
// 保存滚动位置并阻止 body 滚动
|
|
2196
|
+
this.scrollY = window.scrollY;
|
|
2197
|
+
document.body.style.position = 'fixed';
|
|
2198
|
+
document.body.style.top = `-${this.scrollY}px`;
|
|
2199
|
+
document.body.style.width = '100%';
|
|
2200
|
+
// 创建遮罩层和弹窗
|
|
2201
|
+
this.createModal();
|
|
2202
|
+
// 添加事件监听
|
|
2203
|
+
this.attachEventListeners();
|
|
2204
|
+
// 启动倒计时
|
|
2205
|
+
this.startCountdown();
|
|
2264
2206
|
}
|
|
2265
2207
|
/**
|
|
2266
2208
|
* 关闭弹框
|
|
2267
2209
|
*/
|
|
2268
2210
|
close() {
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
this.dropinPayment.destroy();
|
|
2279
|
-
this.dropinPayment = null;
|
|
2211
|
+
if (this.isExiting || !this.overlay)
|
|
2212
|
+
return;
|
|
2213
|
+
this.isExiting = true;
|
|
2214
|
+
// 停止倒计时
|
|
2215
|
+
this.stopCountdown();
|
|
2216
|
+
// 添加退出动画
|
|
2217
|
+
this.overlay.style.animation = 'fadeOut 0.3s ease-in';
|
|
2218
|
+
if (this.modal) {
|
|
2219
|
+
this.modal.style.animation = 'slideOutDown 0.3s ease-in';
|
|
2280
2220
|
}
|
|
2281
|
-
|
|
2221
|
+
// 300ms 后移除元素
|
|
2222
|
+
setTimeout(() => {
|
|
2223
|
+
this.cleanup();
|
|
2224
|
+
this.options.onClose?.();
|
|
2225
|
+
}, 300);
|
|
2282
2226
|
}
|
|
2283
2227
|
/**
|
|
2284
|
-
*
|
|
2228
|
+
* 创建弹框元素
|
|
2285
2229
|
*/
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2230
|
+
createModal() {
|
|
2231
|
+
const { language, productName, purchaseAmount, bonusAmount, discountPrice } = this.options;
|
|
2232
|
+
const isZh = language === 'zh-CN';
|
|
2233
|
+
const texts = isZh ? {
|
|
2234
|
+
title: '你有一笔订单待支付',
|
|
2235
|
+
timePrefix: '请在',
|
|
2236
|
+
timeSuffix: '内支付,超时自动取消',
|
|
2237
|
+
productLabel: '商品:',
|
|
2238
|
+
bonusLabel: '活动赠送:',
|
|
2239
|
+
discountLabel: '优惠价:',
|
|
2240
|
+
cancelButton: '取消',
|
|
2241
|
+
continueButton: '继续支付',
|
|
2242
|
+
} : {
|
|
2243
|
+
title: 'You have a pending order',
|
|
2244
|
+
timePrefix: 'Please pay within',
|
|
2245
|
+
timeSuffix: 'or the order will be automatically cancelled',
|
|
2246
|
+
productLabel: 'Product:',
|
|
2247
|
+
bonusLabel: 'Bonus:',
|
|
2248
|
+
discountLabel: 'Discount:',
|
|
2249
|
+
cancelButton: 'Cancel',
|
|
2250
|
+
continueButton: 'Continue Payment',
|
|
2251
|
+
};
|
|
2252
|
+
// 创建遮罩层
|
|
2253
|
+
this.overlay = document.createElement('div');
|
|
2254
|
+
this.overlay.id = 'retention-modal-overlay';
|
|
2255
|
+
this.overlay.style.cssText = `
|
|
2256
|
+
position: fixed;
|
|
2257
|
+
inset: 0;
|
|
2258
|
+
z-index: 10002;
|
|
2300
2259
|
display: flex;
|
|
2301
2260
|
align-items: center;
|
|
2302
2261
|
justify-content: center;
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2262
|
+
padding: 20px;
|
|
2263
|
+
background: rgba(0, 0, 0, 0.85);
|
|
2264
|
+
backdrop-filter: blur(8px);
|
|
2265
|
+
animation: fadeIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
2306
2266
|
`;
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2267
|
+
// 创建弹框卡片
|
|
2268
|
+
this.modal = document.createElement('div');
|
|
2269
|
+
this.modal.style.cssText = `
|
|
2270
|
+
position: relative;
|
|
2271
|
+
width: 90vw;
|
|
2272
|
+
max-width: 654px;
|
|
2273
|
+
border-radius: 24px;
|
|
2274
|
+
background: linear-gradient(180deg, #2d3748 0%, #1a202c 100%);
|
|
2275
|
+
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8);
|
|
2276
|
+
animation: slideInUp 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
2277
|
+
overflow: hidden;
|
|
2316
2278
|
`;
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2279
|
+
this.modal.innerHTML = `
|
|
2280
|
+
<!-- 关闭按钮 -->
|
|
2281
|
+
<button
|
|
2282
|
+
type="button"
|
|
2283
|
+
id="retention-modal-close-btn"
|
|
2284
|
+
style="
|
|
2285
|
+
position: absolute;
|
|
2286
|
+
top: 20px;
|
|
2287
|
+
right: 20px;
|
|
2288
|
+
z-index: 10;
|
|
2289
|
+
display: flex;
|
|
2290
|
+
width: 32px;
|
|
2291
|
+
height: 32px;
|
|
2292
|
+
align-items: center;
|
|
2293
|
+
justify-content: center;
|
|
2294
|
+
border-radius: 50%;
|
|
2295
|
+
background: rgba(255, 255, 255, 0.1);
|
|
2296
|
+
color: rgba(255, 255, 255, 0.7);
|
|
2297
|
+
border: none;
|
|
2298
|
+
cursor: pointer;
|
|
2299
|
+
transition: all 0.2s;
|
|
2300
|
+
"
|
|
2301
|
+
>
|
|
2302
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
|
|
2303
|
+
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
|
|
2304
|
+
</svg>
|
|
2305
|
+
</button>
|
|
2306
|
+
|
|
2307
|
+
<!-- 头部标题 -->
|
|
2308
|
+
<div style="
|
|
2309
|
+
padding: 40px 32px 24px;
|
|
2310
|
+
text-align: center;
|
|
2311
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
2312
|
+
">
|
|
2313
|
+
<h2 style="
|
|
2314
|
+
margin: 0 0 16px;
|
|
2315
|
+
font-size: 24px;
|
|
2316
|
+
font-weight: 600;
|
|
2317
|
+
color: white;
|
|
2318
|
+
">${texts.title}</h2>
|
|
2319
|
+
|
|
2320
|
+
<!-- 倒计时 -->
|
|
2321
|
+
<p style="
|
|
2322
|
+
margin: 0;
|
|
2323
|
+
font-size: 14px;
|
|
2324
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2325
|
+
">
|
|
2326
|
+
${texts.timePrefix} <span id="countdown-display" style="color: #fc8181; font-weight: 600;">00:15:00</span> ${texts.timeSuffix}
|
|
2327
|
+
</p>
|
|
2328
|
+
</div>
|
|
2329
|
+
|
|
2330
|
+
<!-- 内容区域 -->
|
|
2331
|
+
<div style="padding: 32px;">
|
|
2332
|
+
<!-- 订单详情卡片 -->
|
|
2333
|
+
<div style="
|
|
2334
|
+
border-radius: 16px;
|
|
2335
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
2336
|
+
background: rgba(0, 0, 0, 0.3);
|
|
2337
|
+
padding: 24px;
|
|
2338
|
+
">
|
|
2339
|
+
<!-- 商品信息 -->
|
|
2340
|
+
<div style="
|
|
2341
|
+
display: flex;
|
|
2342
|
+
align-items: center;
|
|
2343
|
+
justify-content: space-between;
|
|
2344
|
+
padding-bottom: 16px;
|
|
2345
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
2346
|
+
margin-bottom: 16px;
|
|
2347
|
+
">
|
|
2348
|
+
<span style="
|
|
2349
|
+
font-size: 14px;
|
|
2350
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2351
|
+
">${texts.productLabel}</span>
|
|
2352
|
+
<span style="
|
|
2353
|
+
font-size: 14px;
|
|
2354
|
+
font-weight: 600;
|
|
2355
|
+
color: white;
|
|
2356
|
+
">${this.escapeHtml(productName)}</span>
|
|
2357
|
+
</div>
|
|
2358
|
+
|
|
2359
|
+
<!-- 活动赠送(可选) -->
|
|
2360
|
+
${bonusAmount ? `
|
|
2361
|
+
<div style="
|
|
2362
|
+
display: flex;
|
|
2363
|
+
align-items: center;
|
|
2364
|
+
justify-content: space-between;
|
|
2365
|
+
padding-bottom: 16px;
|
|
2366
|
+
margin-bottom: 16px;
|
|
2367
|
+
">
|
|
2368
|
+
<span style="
|
|
2369
|
+
font-size: 14px;
|
|
2370
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2371
|
+
">${texts.bonusLabel}</span>
|
|
2372
|
+
<span style="
|
|
2373
|
+
font-size: 14px;
|
|
2374
|
+
font-weight: 600;
|
|
2375
|
+
color: white;
|
|
2376
|
+
">${bonusAmount.toLocaleString()}</span>
|
|
2377
|
+
</div>
|
|
2378
|
+
` : ''}
|
|
2379
|
+
|
|
2380
|
+
<!-- 优惠价 -->
|
|
2381
|
+
<div style="
|
|
2382
|
+
display: flex;
|
|
2383
|
+
align-items: center;
|
|
2384
|
+
justify-content: space-between;
|
|
2385
|
+
background: linear-gradient(90deg, rgba(168, 85, 247, 0.1) 0%, transparent 100%);
|
|
2386
|
+
padding: 16px;
|
|
2387
|
+
margin: 0 -24px -24px;
|
|
2388
|
+
border-radius: 0 0 16px 16px;
|
|
2389
|
+
">
|
|
2390
|
+
<span style="
|
|
2391
|
+
font-size: 14px;
|
|
2392
|
+
color: rgba(255, 255, 255, 0.6);
|
|
2393
|
+
">${texts.discountLabel}</span>
|
|
2394
|
+
<span style="
|
|
2395
|
+
font-size: 18px;
|
|
2396
|
+
font-weight: 700;
|
|
2397
|
+
color: #a855f7;
|
|
2398
|
+
">${this.escapeHtml(discountPrice)}</span>
|
|
2399
|
+
</div>
|
|
2400
|
+
</div>
|
|
2401
|
+
</div>
|
|
2402
|
+
|
|
2403
|
+
<!-- 底部按钮组 -->
|
|
2404
|
+
<div style="
|
|
2405
|
+
display: flex;
|
|
2406
|
+
gap: 12px;
|
|
2407
|
+
padding: 0 32px 32px;
|
|
2408
|
+
">
|
|
2409
|
+
<!-- 取消按钮 -->
|
|
2410
|
+
<button
|
|
2411
|
+
type="button"
|
|
2412
|
+
id="retention-cancel-btn"
|
|
2413
|
+
style="
|
|
2414
|
+
flex: 1;
|
|
2415
|
+
padding: 14px 24px;
|
|
2416
|
+
border-radius: 12px;
|
|
2417
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
2418
|
+
background: transparent;
|
|
2419
|
+
color: white;
|
|
2420
|
+
font-size: 16px;
|
|
2421
|
+
font-weight: 600;
|
|
2422
|
+
cursor: pointer;
|
|
2423
|
+
transition: all 0.2s;
|
|
2424
|
+
"
|
|
2425
|
+
>
|
|
2426
|
+
${texts.cancelButton}
|
|
2427
|
+
</button>
|
|
2428
|
+
|
|
2429
|
+
<!-- 继续支付按钮 -->
|
|
2430
|
+
<button
|
|
2431
|
+
type="button"
|
|
2432
|
+
id="retention-continue-btn"
|
|
2433
|
+
style="
|
|
2434
|
+
flex: 1;
|
|
2435
|
+
padding: 14px 24px;
|
|
2436
|
+
border-radius: 12px;
|
|
2437
|
+
border: none;
|
|
2438
|
+
background: linear-gradient(135deg, #00ff88 0%, #00f2fe 100%);
|
|
2439
|
+
color: white;
|
|
2440
|
+
font-size: 16px;
|
|
2441
|
+
font-weight: 700;
|
|
2442
|
+
cursor: pointer;
|
|
2443
|
+
transition: all 0.2s;
|
|
2444
|
+
box-shadow: 0 4px 16px rgba(0, 255, 136, 0.4);
|
|
2445
|
+
"
|
|
2446
|
+
>
|
|
2447
|
+
${texts.continueButton}
|
|
2448
|
+
</button>
|
|
2449
|
+
</div>
|
|
2450
|
+
`;
|
|
2451
|
+
this.overlay.appendChild(this.modal);
|
|
2452
|
+
document.body.appendChild(this.overlay);
|
|
2453
|
+
// 添加 CSS 动画
|
|
2454
|
+
this.addStyles();
|
|
2455
|
+
}
|
|
2456
|
+
/**
|
|
2457
|
+
* 添加 CSS 动画
|
|
2458
|
+
*/
|
|
2459
|
+
addStyles() {
|
|
2460
|
+
// 检查是否已添加样式
|
|
2461
|
+
if (document.getElementById('retention-modal-styles'))
|
|
2462
|
+
return;
|
|
2463
|
+
const style = document.createElement('style');
|
|
2464
|
+
style.id = 'retention-modal-styles';
|
|
2465
|
+
style.textContent = `
|
|
2466
|
+
@keyframes fadeIn {
|
|
2467
|
+
from { opacity: 0; }
|
|
2468
|
+
to { opacity: 1; }
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
@keyframes fadeOut {
|
|
2472
|
+
from { opacity: 1; }
|
|
2473
|
+
to { opacity: 0; }
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
@keyframes slideInUp {
|
|
2477
|
+
from {
|
|
2478
|
+
opacity: 0;
|
|
2479
|
+
transform: translateY(16px);
|
|
2480
|
+
}
|
|
2481
|
+
to {
|
|
2482
|
+
opacity: 1;
|
|
2483
|
+
transform: translateY(0);
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
@keyframes slideOutDown {
|
|
2488
|
+
from {
|
|
2489
|
+
opacity: 1;
|
|
2490
|
+
transform: translateY(0);
|
|
2491
|
+
}
|
|
2492
|
+
to {
|
|
2493
|
+
opacity: 0;
|
|
2494
|
+
transform: translateY(16px);
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
#retention-modal-close-btn:hover {
|
|
2499
|
+
background: rgba(255, 255, 255, 0.2) !important;
|
|
2500
|
+
color: white !important;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
#retention-cancel-btn:hover {
|
|
2504
|
+
background: rgba(255, 255, 255, 0.1) !important;
|
|
2505
|
+
border-color: rgba(255, 255, 255, 0.3) !important;
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
#retention-continue-btn:hover {
|
|
2509
|
+
box-shadow: 0 6px 20px rgba(0, 255, 136, 0.6) !important;
|
|
2510
|
+
transform: translateY(-1px);
|
|
2511
|
+
}
|
|
2512
|
+
`;
|
|
2513
|
+
document.head.appendChild(style);
|
|
2514
|
+
}
|
|
2515
|
+
/**
|
|
2516
|
+
* 添加事件监听
|
|
2517
|
+
*/
|
|
2518
|
+
attachEventListeners() {
|
|
2519
|
+
if (!this.overlay)
|
|
2520
|
+
return;
|
|
2521
|
+
// 点击遮罩不关闭(挽留弹框需要强制选择)
|
|
2522
|
+
// this.overlay.addEventListener('click', (e) => {
|
|
2523
|
+
// if (e.target === this.overlay) {
|
|
2524
|
+
// this.close();
|
|
2525
|
+
// }
|
|
2526
|
+
// });
|
|
2527
|
+
// 点击关闭按钮
|
|
2528
|
+
const closeBtn = document.getElementById('retention-modal-close-btn');
|
|
2529
|
+
if (closeBtn) {
|
|
2530
|
+
closeBtn.addEventListener('click', () => {
|
|
2531
|
+
this.options.onCancel();
|
|
2532
|
+
this.close();
|
|
2533
|
+
});
|
|
2534
|
+
}
|
|
2535
|
+
// 点击取消按钮
|
|
2536
|
+
const cancelBtn = document.getElementById('retention-cancel-btn');
|
|
2537
|
+
if (cancelBtn) {
|
|
2538
|
+
cancelBtn.addEventListener('click', () => {
|
|
2539
|
+
this.options.onCancel();
|
|
2540
|
+
this.close();
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
// 点击继续支付按钮
|
|
2544
|
+
const continueBtn = document.getElementById('retention-continue-btn');
|
|
2545
|
+
if (continueBtn) {
|
|
2546
|
+
continueBtn.addEventListener('click', () => {
|
|
2547
|
+
this.options.onContinue();
|
|
2548
|
+
this.close();
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
// ESC 键关闭
|
|
2552
|
+
this.boundHandleEscKey = this.handleEscKey.bind(this);
|
|
2553
|
+
document.addEventListener('keydown', this.boundHandleEscKey);
|
|
2554
|
+
}
|
|
2555
|
+
/**
|
|
2556
|
+
* 处理 ESC 键
|
|
2557
|
+
*/
|
|
2558
|
+
handleEscKey(e) {
|
|
2559
|
+
if (e.key === 'Escape') {
|
|
2560
|
+
this.options.onCancel();
|
|
2561
|
+
this.close();
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
/**
|
|
2565
|
+
* 启动倒计时
|
|
2566
|
+
*/
|
|
2567
|
+
startCountdown() {
|
|
2568
|
+
this.remainingSeconds = 15 * 60; // 从15分钟开始倒计时
|
|
2569
|
+
this.updateCountdownDisplay();
|
|
2570
|
+
this.countdownInterval = window.setInterval(() => {
|
|
2571
|
+
this.remainingSeconds--;
|
|
2572
|
+
this.updateCountdownDisplay();
|
|
2573
|
+
if (this.remainingSeconds <= 0) {
|
|
2574
|
+
this.stopCountdown();
|
|
2575
|
+
// 倒计时结束,自动取消订单
|
|
2576
|
+
this.options.onCancel();
|
|
2577
|
+
this.close();
|
|
2578
|
+
}
|
|
2579
|
+
}, 1000);
|
|
2580
|
+
}
|
|
2581
|
+
/**
|
|
2582
|
+
* 停止倒计时
|
|
2583
|
+
*/
|
|
2584
|
+
stopCountdown() {
|
|
2585
|
+
if (this.countdownInterval) {
|
|
2586
|
+
clearInterval(this.countdownInterval);
|
|
2587
|
+
this.countdownInterval = null;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
/**
|
|
2591
|
+
* 更新倒计时显示
|
|
2592
|
+
*/
|
|
2593
|
+
updateCountdownDisplay() {
|
|
2594
|
+
const display = document.getElementById('countdown-display');
|
|
2595
|
+
if (!display)
|
|
2596
|
+
return;
|
|
2597
|
+
const hours = Math.floor(this.remainingSeconds / 3600);
|
|
2598
|
+
const minutes = Math.floor((this.remainingSeconds % 3600) / 60);
|
|
2599
|
+
const seconds = this.remainingSeconds % 60;
|
|
2600
|
+
display.textContent = `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
2601
|
+
}
|
|
2602
|
+
/**
|
|
2603
|
+
* 清理资源
|
|
2604
|
+
*/
|
|
2605
|
+
cleanup() {
|
|
2606
|
+
// 停止倒计时
|
|
2607
|
+
this.stopCountdown();
|
|
2608
|
+
// 移除事件监听
|
|
2609
|
+
if (this.boundHandleEscKey) {
|
|
2610
|
+
document.removeEventListener('keydown', this.boundHandleEscKey);
|
|
2611
|
+
this.boundHandleEscKey = null;
|
|
2612
|
+
}
|
|
2613
|
+
// 移除元素
|
|
2614
|
+
if (this.overlay && this.overlay.parentNode) {
|
|
2615
|
+
this.overlay.parentNode.removeChild(this.overlay);
|
|
2616
|
+
}
|
|
2617
|
+
// 恢复 body 滚动
|
|
2618
|
+
document.body.style.position = '';
|
|
2619
|
+
document.body.style.top = '';
|
|
2620
|
+
document.body.style.width = '';
|
|
2621
|
+
window.scrollTo(0, this.scrollY);
|
|
2622
|
+
// 清空引用
|
|
2623
|
+
this.overlay = null;
|
|
2624
|
+
this.modal = null;
|
|
2625
|
+
this.isExiting = false;
|
|
2626
|
+
}
|
|
2627
|
+
/**
|
|
2628
|
+
* HTML 转义工具函数
|
|
2629
|
+
* 防止 XSS 攻击
|
|
2630
|
+
*/
|
|
2631
|
+
escapeHtml(text) {
|
|
2632
|
+
const div = document.createElement('div');
|
|
2633
|
+
div.textContent = text;
|
|
2634
|
+
return div.innerHTML;
|
|
2635
|
+
}
|
|
2636
|
+
/**
|
|
2637
|
+
* 检查弹框是否打开
|
|
2638
|
+
*/
|
|
2639
|
+
isOpen() {
|
|
2640
|
+
return this.overlay !== null && !this.isExiting;
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
/**
|
|
2645
|
+
* DropinPaymentModal - Dropin 支付弹框
|
|
2646
|
+
* 封装 DropinPaymentComponent 并在弹框中展示
|
|
2647
|
+
*/
|
|
2648
|
+
class DropinPaymentModal {
|
|
2649
|
+
constructor(paymentInstance, orderId, accountToken, paymentMethod, options) {
|
|
2650
|
+
this.paymentInstance = paymentInstance;
|
|
2651
|
+
this.orderId = orderId;
|
|
2652
|
+
this.accountToken = accountToken;
|
|
2653
|
+
this.paymentMethod = paymentMethod;
|
|
2654
|
+
this.options = options;
|
|
2655
|
+
this.dropinPayment = null;
|
|
2656
|
+
this.containerElement = null;
|
|
2657
|
+
this.retentionModal = null;
|
|
2658
|
+
this.shouldShowRetention = true; // 控制是否显示挽留弹框
|
|
2659
|
+
this.paymentCompleted = false; // 标记支付是否已完成
|
|
2660
|
+
// 创建弹框
|
|
2661
|
+
this.modal = new PaymentModal({
|
|
2662
|
+
title: options.modalTitle ?? `Pay with ${paymentMethod.payment_method_name}`,
|
|
2663
|
+
showCloseButton: true,
|
|
2664
|
+
closeOnOverlayClick: false, // Dropin 支付中不允许点击遮罩关闭
|
|
2665
|
+
closeOnEsc: false, // 禁用 ESC 键关闭,改为触发挽留弹框
|
|
2666
|
+
maxWidth: '600px',
|
|
2667
|
+
onClose: () => {
|
|
2668
|
+
// 当弹框真正关闭时触发挽留弹框
|
|
2669
|
+
if (this.shouldShowRetention && this.options.enableRetention !== false && !this.paymentCompleted) {
|
|
2670
|
+
this.showRetentionModal();
|
|
2671
|
+
}
|
|
2672
|
+
else {
|
|
2673
|
+
this.cleanup();
|
|
2674
|
+
}
|
|
2675
|
+
},
|
|
2676
|
+
...options.modalOptions,
|
|
2677
|
+
});
|
|
2678
|
+
console.log('[DropinPaymentModal] Created:', {
|
|
2679
|
+
orderId: this.orderId,
|
|
2680
|
+
paymentMethod: this.paymentMethod.payment_method_name,
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
/**
|
|
2684
|
+
* 打开弹框并渲染支付组件
|
|
2685
|
+
*/
|
|
2686
|
+
async open() {
|
|
2687
|
+
console.log('[DropinPaymentModal] Opening modal...');
|
|
2688
|
+
// 打开弹框
|
|
2689
|
+
this.modal.open();
|
|
2690
|
+
// 创建容器元素
|
|
2691
|
+
this.containerElement = document.createElement('div');
|
|
2692
|
+
this.containerElement.id = 'dropin-payment-container';
|
|
2693
|
+
this.containerElement.style.cssText = `
|
|
2694
|
+
min-height: 500px;
|
|
2695
|
+
width: 100%;
|
|
2696
|
+
`;
|
|
2697
|
+
// 设置弹框内容 - 必须先添加到 DOM,再创建 Dropin 组件
|
|
2698
|
+
const contentContainer = this.modal.getContentContainer();
|
|
2699
|
+
if (!contentContainer) {
|
|
2700
|
+
throw new Error('Modal content container not found');
|
|
2701
|
+
}
|
|
2702
|
+
contentContainer.appendChild(this.containerElement);
|
|
2703
|
+
// 验证订单信息(关键:确保后端订单已创建)
|
|
2704
|
+
try {
|
|
2705
|
+
console.log('[DropinPaymentModal] Validating order info...');
|
|
2706
|
+
const response = await this.paymentInstance.getOrderInfo();
|
|
2707
|
+
if (response.err || !response.data?.order_info) {
|
|
2708
|
+
throw new Error(response.message || 'Order information not found');
|
|
2709
|
+
}
|
|
2710
|
+
const orderInfo = response.data.order_info;
|
|
2711
|
+
if (!orderInfo.sys_order_id) {
|
|
2712
|
+
throw new Error('Order ID (sys_order_id) is missing');
|
|
2713
|
+
}
|
|
2714
|
+
console.log('[DropinPaymentModal] Order validated:', {
|
|
2715
|
+
orderInfo,
|
|
2716
|
+
orderId: orderInfo.sys_order_id,
|
|
2717
|
+
status: orderInfo.order_status,
|
|
2718
|
+
});
|
|
2719
|
+
}
|
|
2720
|
+
catch (error) {
|
|
2721
|
+
console.error('[DropinPaymentModal] Order validation failed:', error);
|
|
2722
|
+
this.close();
|
|
2723
|
+
throw new Error(`Order validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
2724
|
+
}
|
|
2725
|
+
// 创建并渲染 Dropin 支付组件
|
|
2726
|
+
try {
|
|
2727
|
+
this.dropinPayment = new DropinPaymentComponent({
|
|
2728
|
+
paymentInstance: this.paymentInstance,
|
|
2729
|
+
orderId: this.orderId,
|
|
2730
|
+
accountToken: this.accountToken,
|
|
2731
|
+
paymentMethod: this.paymentMethod,
|
|
2732
|
+
containerId: '#dropin-payment-container',
|
|
2733
|
+
onSubmit: (payload) => {
|
|
2734
|
+
console.log('[DropinPaymentModal] onSubmit:', payload);
|
|
2735
|
+
this.options.onSubmit?.(payload);
|
|
2736
|
+
},
|
|
2737
|
+
onError: (payload, error) => {
|
|
2738
|
+
console.error('[DropinPaymentModal] onError:', error);
|
|
2739
|
+
this.options.onError?.(payload, error);
|
|
2740
|
+
},
|
|
2741
|
+
onCreateOrder: (payload) => {
|
|
2742
|
+
console.log('[DropinPaymentModal] onCreateOrder:', payload);
|
|
2743
|
+
this.options.onCreateOrder?.(payload);
|
|
2744
|
+
},
|
|
2745
|
+
onCompleted: (payload) => {
|
|
2746
|
+
console.log('[DropinPaymentModal] onCompleted:', payload);
|
|
2747
|
+
this.paymentCompleted = true; // 标记支付已完成
|
|
2748
|
+
this.shouldShowRetention = false; // 支付完成后不显示挽留弹框
|
|
2749
|
+
this.options.onCompleted?.(payload);
|
|
2750
|
+
// 支付成功后自动关闭弹框(可选)
|
|
2751
|
+
setTimeout(() => this.close(), 2000);
|
|
2752
|
+
},
|
|
2753
|
+
onFailed: (payload) => {
|
|
2754
|
+
console.error('[DropinPaymentModal] onFailed:', payload);
|
|
2755
|
+
this.options.onFailed?.(payload);
|
|
2756
|
+
},
|
|
2757
|
+
onLoading: (loading) => {
|
|
2758
|
+
this.options.onLoading?.(loading);
|
|
2759
|
+
// 可以在这里显示加载状态
|
|
2760
|
+
if (loading) {
|
|
2761
|
+
this.showLoading();
|
|
2762
|
+
}
|
|
2763
|
+
else {
|
|
2764
|
+
this.hideLoading();
|
|
2765
|
+
}
|
|
2766
|
+
},
|
|
2767
|
+
});
|
|
2768
|
+
await this.dropinPayment.render();
|
|
2769
|
+
console.log('[DropinPaymentModal] Dropin component rendered');
|
|
2770
|
+
}
|
|
2771
|
+
catch (error) {
|
|
2772
|
+
console.error('[DropinPaymentModal] Failed to render dropin:', error);
|
|
2773
|
+
this.close();
|
|
2774
|
+
throw error;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* 关闭弹框
|
|
2779
|
+
*/
|
|
2780
|
+
close() {
|
|
2781
|
+
console.log('[DropinPaymentModal] Closing modal...');
|
|
2782
|
+
// 关闭挽留弹框(如果存在)
|
|
2783
|
+
if (this.retentionModal && this.retentionModal.isOpen()) {
|
|
2784
|
+
this.retentionModal.close();
|
|
2785
|
+
}
|
|
2786
|
+
this.modal.close();
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* 清理资源
|
|
2790
|
+
*/
|
|
2791
|
+
cleanup() {
|
|
2792
|
+
console.log('[DropinPaymentModal] Cleaning up...');
|
|
2793
|
+
// 清理 Dropin 支付组件
|
|
2794
|
+
if (this.dropinPayment) {
|
|
2795
|
+
this.dropinPayment.destroy();
|
|
2796
|
+
this.dropinPayment = null;
|
|
2797
|
+
}
|
|
2798
|
+
// 清理挽留弹框
|
|
2799
|
+
if (this.retentionModal) {
|
|
2800
|
+
if (this.retentionModal.isOpen()) {
|
|
2801
|
+
this.retentionModal.close();
|
|
2802
|
+
}
|
|
2803
|
+
this.retentionModal = null;
|
|
2804
|
+
}
|
|
2805
|
+
this.containerElement = null;
|
|
2806
|
+
this.shouldShowRetention = true; // 重置状态
|
|
2807
|
+
this.paymentCompleted = false; // 重置状态
|
|
2808
|
+
}
|
|
2809
|
+
/**
|
|
2810
|
+
* 显示加载状态
|
|
2811
|
+
*/
|
|
2812
|
+
showLoading() {
|
|
2813
|
+
if (!this.containerElement)
|
|
2814
|
+
return;
|
|
2815
|
+
const loadingEl = document.getElementById('dropin-loading');
|
|
2816
|
+
if (loadingEl)
|
|
2817
|
+
return; // 已经存在
|
|
2818
|
+
const loading = document.createElement('div');
|
|
2819
|
+
loading.id = 'dropin-loading';
|
|
2820
|
+
loading.style.cssText = `
|
|
2821
|
+
position: absolute;
|
|
2822
|
+
top: 0;
|
|
2823
|
+
left: 0;
|
|
2824
|
+
right: 0;
|
|
2825
|
+
bottom: 0;
|
|
2826
|
+
display: flex;
|
|
2827
|
+
align-items: center;
|
|
2828
|
+
justify-content: center;
|
|
2829
|
+
background: rgba(0, 0, 0, 0.3);
|
|
2830
|
+
backdrop-filter: blur(2px);
|
|
2831
|
+
z-index: 10;
|
|
2832
|
+
`;
|
|
2833
|
+
loading.innerHTML = `
|
|
2834
|
+
<div style="
|
|
2835
|
+
width: 40px;
|
|
2836
|
+
height: 40px;
|
|
2837
|
+
border: 3px solid rgba(255, 255, 255, 0.2);
|
|
2838
|
+
border-top-color: #ffffff;
|
|
2839
|
+
border-radius: 50%;
|
|
2840
|
+
animation: spin 0.8s linear infinite;
|
|
2841
|
+
"></div>
|
|
2842
|
+
`;
|
|
2843
|
+
// 添加旋转动画
|
|
2844
|
+
if (!document.getElementById('dropin-loading-styles')) {
|
|
2845
|
+
const style = document.createElement('style');
|
|
2846
|
+
style.id = 'dropin-loading-styles';
|
|
2847
|
+
style.textContent = `
|
|
2848
|
+
@keyframes spin {
|
|
2849
|
+
to { transform: rotate(360deg); }
|
|
2850
|
+
}
|
|
2851
|
+
`;
|
|
2852
|
+
document.head.appendChild(style);
|
|
2853
|
+
}
|
|
2854
|
+
this.containerElement.style.position = 'relative';
|
|
2855
|
+
this.containerElement.appendChild(loading);
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* 隐藏加载状态
|
|
2859
|
+
*/
|
|
2860
|
+
hideLoading() {
|
|
2861
|
+
const loadingEl = document.getElementById('dropin-loading');
|
|
2862
|
+
if (loadingEl && loadingEl.parentNode) {
|
|
2863
|
+
loadingEl.parentNode.removeChild(loadingEl);
|
|
2864
|
+
}
|
|
2865
|
+
}
|
|
2866
|
+
/**
|
|
2867
|
+
* 显示挽留弹框
|
|
2868
|
+
*/
|
|
2869
|
+
async showRetentionModal() {
|
|
2870
|
+
console.log('[DropinPaymentModal] Showing retention modal...');
|
|
2871
|
+
try {
|
|
2872
|
+
// 获取订单信息用于挽留弹框展示
|
|
2873
|
+
const response = await this.paymentInstance.getOrderInfo();
|
|
2874
|
+
if (response.err || !response.data?.order_info) {
|
|
2875
|
+
console.warn('[DropinPaymentModal] Failed to get order info for retention modal');
|
|
2876
|
+
this.cleanup();
|
|
2877
|
+
return;
|
|
2878
|
+
}
|
|
2879
|
+
const orderInfo = response.data.order_info;
|
|
2880
|
+
// 创建挽留弹框
|
|
2881
|
+
this.retentionModal = new RetentionModal({
|
|
2882
|
+
language: this.options.retentionOptions?.language ?? 'en',
|
|
2883
|
+
productName: orderInfo.product_name || this.paymentMethod.payment_method_name,
|
|
2884
|
+
purchaseAmount: Number(orderInfo.quantity) || 0,
|
|
2885
|
+
bonusAmount: this.options.retentionOptions?.bonusAmount,
|
|
2886
|
+
discountPrice: formatPrice(orderInfo.price || orderInfo.mark_price || 0, orderInfo.currency || 'USD'),
|
|
2887
|
+
onContinue: () => {
|
|
2888
|
+
// 继续支付:重新打开支付弹框
|
|
2889
|
+
console.log('[DropinPaymentModal] User chose to continue payment');
|
|
2890
|
+
this.shouldShowRetention = false; // 防止再次触发挽留弹框
|
|
2891
|
+
this.open(); // 重新打开支付弹框
|
|
2892
|
+
},
|
|
2893
|
+
onCancel: () => {
|
|
2894
|
+
// 确认取消:关闭所有弹框
|
|
2895
|
+
console.log('[DropinPaymentModal] User confirmed cancellation');
|
|
2896
|
+
this.cleanup();
|
|
2897
|
+
},
|
|
2898
|
+
onClose: () => {
|
|
2899
|
+
// 挽留弹框关闭时清理
|
|
2900
|
+
this.retentionModal = null;
|
|
2901
|
+
},
|
|
2902
|
+
...this.options.retentionOptions,
|
|
2903
|
+
});
|
|
2904
|
+
this.retentionModal.open();
|
|
2905
|
+
}
|
|
2906
|
+
catch (error) {
|
|
2907
|
+
console.error('[DropinPaymentModal] Failed to show retention modal:', error);
|
|
2908
|
+
this.cleanup();
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
/**
|
|
2912
|
+
* 检查弹框是否打开
|
|
2913
|
+
*/
|
|
2914
|
+
isOpen() {
|
|
2915
|
+
return this.modal.isModalOpen();
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
/**
|
|
2920
|
+
* SeaartPaymentSDK
|
|
2921
|
+
* 基于 SeaartPaymentComponent 的支付 SDK 封装
|
|
2922
|
+
*
|
|
2923
|
+
* 核心职责:
|
|
2924
|
+
* 1. 动态加载 SeaartPaymentComponent 脚本
|
|
2925
|
+
* 2. 全局初始化(仅一次)
|
|
2926
|
+
* 3. 获取支付方式列表
|
|
2927
|
+
* 4. 创建订单支付实例
|
|
2928
|
+
*/
|
|
2929
|
+
class SeaartPaymentSDK {
|
|
2930
|
+
/**
|
|
2360
2931
|
* 私有构造函数(单例模式)
|
|
2361
2932
|
*/
|
|
2362
2933
|
constructor() {
|
|
@@ -3579,71 +4150,423 @@ class PurchaseSuccessModal {
|
|
|
3579
4150
|
document.head.appendChild(style);
|
|
3580
4151
|
}
|
|
3581
4152
|
/**
|
|
3582
|
-
* 添加事件监听
|
|
4153
|
+
* 添加事件监听
|
|
4154
|
+
*/
|
|
4155
|
+
attachEventListeners() {
|
|
4156
|
+
if (!this.overlay)
|
|
4157
|
+
return;
|
|
4158
|
+
// 点击遮罩关闭
|
|
4159
|
+
this.overlay.addEventListener('click', (e) => {
|
|
4160
|
+
if (e.target === this.overlay) {
|
|
4161
|
+
this.close();
|
|
4162
|
+
}
|
|
4163
|
+
});
|
|
4164
|
+
// 点击关闭按钮
|
|
4165
|
+
const closeBtn = document.getElementById('success-modal-close-btn');
|
|
4166
|
+
if (closeBtn) {
|
|
4167
|
+
closeBtn.addEventListener('click', () => this.close());
|
|
4168
|
+
}
|
|
4169
|
+
// ESC 键关闭
|
|
4170
|
+
this.boundHandleEscKey = this.handleEscKey.bind(this);
|
|
4171
|
+
document.addEventListener('keydown', this.boundHandleEscKey);
|
|
4172
|
+
}
|
|
4173
|
+
/**
|
|
4174
|
+
* 处理 ESC 键
|
|
4175
|
+
*/
|
|
4176
|
+
handleEscKey(e) {
|
|
4177
|
+
if (e.key === 'Escape') {
|
|
4178
|
+
this.close();
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
4181
|
+
/**
|
|
4182
|
+
* 清理资源
|
|
4183
|
+
*/
|
|
4184
|
+
cleanup() {
|
|
4185
|
+
// 移除事件监听
|
|
4186
|
+
if (this.boundHandleEscKey) {
|
|
4187
|
+
document.removeEventListener('keydown', this.boundHandleEscKey);
|
|
4188
|
+
this.boundHandleEscKey = null;
|
|
4189
|
+
}
|
|
4190
|
+
// 移除元素
|
|
4191
|
+
if (this.overlay && this.overlay.parentNode) {
|
|
4192
|
+
this.overlay.parentNode.removeChild(this.overlay);
|
|
4193
|
+
}
|
|
4194
|
+
// 恢复 body 滚动
|
|
4195
|
+
document.body.style.position = '';
|
|
4196
|
+
document.body.style.top = '';
|
|
4197
|
+
document.body.style.width = '';
|
|
4198
|
+
window.scrollTo(0, this.scrollY);
|
|
4199
|
+
// 清空引用
|
|
4200
|
+
this.overlay = null;
|
|
4201
|
+
this.modal = null;
|
|
4202
|
+
this.isExiting = false;
|
|
4203
|
+
}
|
|
4204
|
+
/**
|
|
4205
|
+
* HTML 转义工具函数
|
|
4206
|
+
* 防止 XSS 攻击
|
|
4207
|
+
*/
|
|
4208
|
+
escapeHtml(text) {
|
|
4209
|
+
const div = document.createElement('div');
|
|
4210
|
+
div.textContent = text;
|
|
4211
|
+
return div.innerHTML;
|
|
4212
|
+
}
|
|
4213
|
+
/**
|
|
4214
|
+
* 检查弹窗是否打开
|
|
4215
|
+
*/
|
|
4216
|
+
isOpen() {
|
|
4217
|
+
return this.overlay !== null && !this.isExiting;
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
/**
|
|
4222
|
+
* BasePackageModal - Abstract base class for package modals
|
|
4223
|
+
* Provides shared logic for SDK initialization, payment flow, and event handling
|
|
4224
|
+
*/
|
|
4225
|
+
/**
|
|
4226
|
+
* Abstract base class for package modals
|
|
4227
|
+
* Provides common functionality for SDK initialization, payment flow, and event handling
|
|
4228
|
+
*/
|
|
4229
|
+
class BasePackageModal {
|
|
4230
|
+
// === Constructor ===
|
|
4231
|
+
constructor(options) {
|
|
4232
|
+
this.resizeHandler = null;
|
|
4233
|
+
this.isInitializingSDK = false;
|
|
4234
|
+
this.sdkInitialized = false;
|
|
4235
|
+
this.options = options;
|
|
4236
|
+
this.language = options.language || 'en';
|
|
4237
|
+
this.modal = this.createModal();
|
|
4238
|
+
console.log(`[${this.constructor.name}] Created`);
|
|
4239
|
+
}
|
|
4240
|
+
// === Public Methods ===
|
|
4241
|
+
/**
|
|
4242
|
+
* Open the modal
|
|
4243
|
+
*/
|
|
4244
|
+
async open() {
|
|
4245
|
+
console.log(`[${this.constructor.name}] Opening modal...`);
|
|
4246
|
+
this.modal.open();
|
|
4247
|
+
// Hook for subclass-specific styling
|
|
4248
|
+
this.applyModalStyling();
|
|
4249
|
+
// Render content (abstract method)
|
|
4250
|
+
this.renderContent();
|
|
4251
|
+
// Add resize listener
|
|
4252
|
+
this.resizeHandler = () => this.renderContent();
|
|
4253
|
+
window.addEventListener('resize', this.resizeHandler);
|
|
4254
|
+
// Initialize SDK in background
|
|
4255
|
+
if (!this.sdkInitialized && !this.isInitializingSDK) {
|
|
4256
|
+
this.initializeSDK();
|
|
4257
|
+
}
|
|
4258
|
+
console.log(`[${this.constructor.name}] Modal opened`);
|
|
4259
|
+
}
|
|
4260
|
+
/**
|
|
4261
|
+
* Close the modal
|
|
4262
|
+
*/
|
|
4263
|
+
close() {
|
|
4264
|
+
console.log(`[${this.constructor.name}] Closing modal...`);
|
|
4265
|
+
this.modal.close();
|
|
4266
|
+
}
|
|
4267
|
+
/**
|
|
4268
|
+
* Check if modal is open
|
|
4269
|
+
*/
|
|
4270
|
+
isOpen() {
|
|
4271
|
+
return this.modal.isModalOpen();
|
|
4272
|
+
}
|
|
4273
|
+
// === Protected Hook Methods (can be overridden by subclasses) ===
|
|
4274
|
+
/**
|
|
4275
|
+
* Apply modal styling (hook method)
|
|
4276
|
+
* Subclasses can override to customize modal appearance
|
|
4277
|
+
*/
|
|
4278
|
+
applyModalStyling() {
|
|
4279
|
+
// Default: no custom styling
|
|
4280
|
+
}
|
|
4281
|
+
// === Protected Shared Methods ===
|
|
4282
|
+
/**
|
|
4283
|
+
* Initialize payment SDK (identical in both classes)
|
|
4284
|
+
*/
|
|
4285
|
+
async initializeSDK() {
|
|
4286
|
+
if (this.isInitializingSDK || this.sdkInitialized) {
|
|
4287
|
+
return;
|
|
4288
|
+
}
|
|
4289
|
+
if (!this.options.sdkConfig) {
|
|
4290
|
+
console.log(`[${this.constructor.name}] No SDK configuration provided, skipping initialization`);
|
|
4291
|
+
return;
|
|
4292
|
+
}
|
|
4293
|
+
this.isInitializingSDK = true;
|
|
4294
|
+
console.log(`[${this.constructor.name}] Initializing payment SDK...`);
|
|
4295
|
+
// Show loading indicator
|
|
4296
|
+
const loader = showLoadingIndicator('Initializing payment system...');
|
|
4297
|
+
try {
|
|
4298
|
+
const config = this.options.sdkConfig;
|
|
4299
|
+
// 1. Get base configuration from environment
|
|
4300
|
+
const envConfig = ENVIRONMENT_CONFIGS[config.environment];
|
|
4301
|
+
// 2. Merge configuration (custom config has higher priority)
|
|
4302
|
+
const finalConfig = {
|
|
4303
|
+
scriptUrl: config.scriptUrl || envConfig.scriptUrl,
|
|
4304
|
+
clientId: config.clientId || envConfig.clientId,
|
|
4305
|
+
orderApiUrl: config.orderApiUrl || envConfig.orderApiUrl,
|
|
4306
|
+
cssUrl: config.cssUrl || envConfig.cssUrl,
|
|
4307
|
+
};
|
|
4308
|
+
console.log(`[${this.constructor.name}] Using environment:`, config.environment);
|
|
4309
|
+
// 3. Initialize SeaartPaymentSDK
|
|
4310
|
+
await SeaartPaymentSDK.getInstance().init({
|
|
4311
|
+
scriptUrl: finalConfig.scriptUrl,
|
|
4312
|
+
clientId: finalConfig.clientId,
|
|
4313
|
+
language: 'en',
|
|
4314
|
+
scriptTimeout: config.scriptTimeout,
|
|
4315
|
+
cssUrl: finalConfig.cssUrl,
|
|
4316
|
+
});
|
|
4317
|
+
// 4. Get payment methods list
|
|
4318
|
+
const paymentMethods = await SeaartPaymentSDK.getInstance().getPaymentMethods({
|
|
4319
|
+
country_code: config.countryCode,
|
|
4320
|
+
business_type: config.businessType ?? 1, // Default to 1 (one-time purchase)
|
|
4321
|
+
});
|
|
4322
|
+
// 5. Find matching payment method
|
|
4323
|
+
const paymentMethod = config.paymentMethodType
|
|
4324
|
+
? paymentMethods.find((m) => m.payment_method_type === config.paymentMethodType ||
|
|
4325
|
+
m.payment_method_name.toLowerCase().includes(config.paymentMethodType.toLowerCase()))
|
|
4326
|
+
: paymentMethods.find((m) => m.payment_type === 2); // Default to dropin (payment_type === 2)
|
|
4327
|
+
if (!paymentMethod) {
|
|
4328
|
+
throw new Error(`Payment method "${config.paymentMethodType || 'dropin'}" not found`);
|
|
4329
|
+
}
|
|
4330
|
+
// 6. Store to class members (including finalConfig for later use)
|
|
4331
|
+
this.paymentMethod = paymentMethod;
|
|
4332
|
+
this.accountToken = config.accountToken;
|
|
4333
|
+
this.sdkInitialized = true;
|
|
4334
|
+
// Store final config to config object (for handlePaymentFlow)
|
|
4335
|
+
this.options.sdkConfig._resolvedOrderApiUrl = finalConfig.orderApiUrl;
|
|
4336
|
+
console.log(`[${this.constructor.name}] SDK initialized with environment config:`, {
|
|
4337
|
+
environment: config.environment,
|
|
4338
|
+
paymentMethod: paymentMethod.payment_method_name,
|
|
4339
|
+
accountToken: config.accountToken ? 'provided' : 'not provided',
|
|
4340
|
+
});
|
|
4341
|
+
}
|
|
4342
|
+
catch (error) {
|
|
4343
|
+
console.error(`[${this.constructor.name}] Failed to initialize payment SDK:`, error);
|
|
4344
|
+
// SDK initialization failure does not affect browsing packages, just cannot make payments
|
|
4345
|
+
}
|
|
4346
|
+
finally {
|
|
4347
|
+
this.isInitializingSDK = false;
|
|
4348
|
+
// Hide loading indicator
|
|
4349
|
+
hideLoadingIndicator(loader);
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
4352
|
+
/**
|
|
4353
|
+
* Wait for SDK initialization with timeout and retry
|
|
4354
|
+
* @param timeout Timeout in milliseconds (default 30 seconds)
|
|
4355
|
+
* @param maxRetries Maximum retry count (default 1)
|
|
4356
|
+
* @returns Whether initialization succeeded
|
|
4357
|
+
*/
|
|
4358
|
+
async waitForSDKInitialization(timeout = 30000, maxRetries = 1) {
|
|
4359
|
+
const startTime = Date.now();
|
|
4360
|
+
// If already initialized, return immediately
|
|
4361
|
+
if (this.sdkInitialized) {
|
|
4362
|
+
console.log(`[${this.constructor.name}] SDK already initialized`);
|
|
4363
|
+
return true;
|
|
4364
|
+
}
|
|
4365
|
+
// If not started initializing and has config, trigger initialization
|
|
4366
|
+
if (!this.isInitializingSDK && this.options.sdkConfig) {
|
|
4367
|
+
console.log(`[${this.constructor.name}] Starting SDK initialization...`);
|
|
4368
|
+
await this.initializeSDK();
|
|
4369
|
+
if (this.sdkInitialized) {
|
|
4370
|
+
return true;
|
|
4371
|
+
}
|
|
4372
|
+
}
|
|
4373
|
+
// Wait for initialization to complete
|
|
4374
|
+
console.log(`[${this.constructor.name}] Waiting for SDK initialization...`);
|
|
4375
|
+
while (Date.now() - startTime < timeout) {
|
|
4376
|
+
if (this.sdkInitialized) {
|
|
4377
|
+
console.log(`[${this.constructor.name}] SDK initialization completed`);
|
|
4378
|
+
return true;
|
|
4379
|
+
}
|
|
4380
|
+
if (!this.isInitializingSDK) {
|
|
4381
|
+
// Initialization ended but not successful, try retry
|
|
4382
|
+
if (maxRetries > 0) {
|
|
4383
|
+
console.log(`[${this.constructor.name}] SDK initialization failed, retrying... (${maxRetries} retries left)`);
|
|
4384
|
+
await this.initializeSDK();
|
|
4385
|
+
return this.waitForSDKInitialization(timeout - (Date.now() - startTime), maxRetries - 1);
|
|
4386
|
+
}
|
|
4387
|
+
else {
|
|
4388
|
+
console.error(`[${this.constructor.name}] SDK initialization failed after all retries`);
|
|
4389
|
+
return false;
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
// Wait 100ms before retry
|
|
4393
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4394
|
+
}
|
|
4395
|
+
// Timeout
|
|
4396
|
+
console.error(`[${this.constructor.name}] SDK initialization timed out`);
|
|
4397
|
+
return false;
|
|
4398
|
+
}
|
|
4399
|
+
/**
|
|
4400
|
+
* Handle payment flow (order creation + payment modal)
|
|
4401
|
+
*/
|
|
4402
|
+
async handlePaymentFlow(pkg, button, originalHTML) {
|
|
4403
|
+
try {
|
|
4404
|
+
// Update button state to "Creating order"
|
|
4405
|
+
button.innerHTML = this.getLoadingButtonHTML('Creating order...');
|
|
4406
|
+
console.log(`[${this.constructor.name}] Creating order for package:`, pkg.id);
|
|
4407
|
+
// Use default implementation: call resolved orderApiUrl
|
|
4408
|
+
const resolvedOrderApiUrl = this.options.sdkConfig._resolvedOrderApiUrl || ENVIRONMENT_CONFIGS[this.options.sdkConfig.environment].orderApiUrl;
|
|
4409
|
+
const response = await createOrder(resolvedOrderApiUrl, this.options.sdkConfig.accountToken || '', {
|
|
4410
|
+
product_id: pkg.id,
|
|
4411
|
+
purchase_type: this.options.sdkConfig.businessType ?? 1, // Default to 1
|
|
4412
|
+
});
|
|
4413
|
+
console.log(`[${this.constructor.name}] Create order response:`, response);
|
|
4414
|
+
if (!response || !response.transaction_id) {
|
|
4415
|
+
throw new Error('Failed to create order: Invalid response from API');
|
|
4416
|
+
}
|
|
4417
|
+
const orderId = response.transaction_id;
|
|
4418
|
+
console.log(`[${this.constructor.name}] Order created:`, orderId);
|
|
4419
|
+
// Restore button state
|
|
4420
|
+
button.disabled = false;
|
|
4421
|
+
button.innerHTML = originalHTML;
|
|
4422
|
+
// Create and open payment modal
|
|
4423
|
+
await this.openPaymentModal(orderId, pkg);
|
|
4424
|
+
}
|
|
4425
|
+
catch (error) {
|
|
4426
|
+
console.error(`[${this.constructor.name}] Payment flow failed:`, error);
|
|
4427
|
+
// Restore button state
|
|
4428
|
+
button.disabled = false;
|
|
4429
|
+
button.innerHTML = originalHTML;
|
|
4430
|
+
// Show error message (use custom UI instead of alert)
|
|
4431
|
+
showErrorMessage(`Payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
4432
|
+
// Trigger failure callback
|
|
4433
|
+
this.options.onPaymentFailed?.(error instanceof Error ? error : new Error(String(error)), pkg);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
/**
|
|
4437
|
+
* Open payment modal (DropinPaymentModal + PurchaseSuccessModal)
|
|
4438
|
+
*/
|
|
4439
|
+
async openPaymentModal(orderId, pkg) {
|
|
4440
|
+
if (!this.paymentMethod) {
|
|
4441
|
+
throw new Error('Payment method not configured');
|
|
4442
|
+
}
|
|
4443
|
+
const pkgName = this.getPackageDisplayName(pkg);
|
|
4444
|
+
// Create payment instance (using orderId)
|
|
4445
|
+
if (!this.sdkInitialized) {
|
|
4446
|
+
throw new Error('Payment SDK not initialized. Please provide sdkConfig.');
|
|
4447
|
+
}
|
|
4448
|
+
const paymentInstance = window.SeaartPaymentComponent.createPayment({
|
|
4449
|
+
sys_order_id: orderId,
|
|
4450
|
+
account_token: this.accountToken,
|
|
4451
|
+
});
|
|
4452
|
+
const dropinModal = new DropinPaymentModal(paymentInstance, orderId, this.accountToken, this.paymentMethod, {
|
|
4453
|
+
modalTitle: `Purchase ${pkgName}`,
|
|
4454
|
+
onCompleted: (payload) => {
|
|
4455
|
+
console.log(`[${this.constructor.name}] Payment completed:`, payload);
|
|
4456
|
+
this.close();
|
|
4457
|
+
// Show purchase success modal
|
|
4458
|
+
const successModal = new PurchaseSuccessModal({
|
|
4459
|
+
data: {
|
|
4460
|
+
packName: pkgName,
|
|
4461
|
+
credits: parseInt(pkg.credits),
|
|
4462
|
+
amount: pkg.price,
|
|
4463
|
+
currency: pkg.currency === 'USD' ? '$' : pkg.currency,
|
|
4464
|
+
orderId: orderId,
|
|
4465
|
+
transactionId: payload.transaction_id,
|
|
4466
|
+
},
|
|
4467
|
+
language: this.language,
|
|
4468
|
+
onClose: () => {
|
|
4469
|
+
// Refresh credits after modal closes
|
|
4470
|
+
(async () => {
|
|
4471
|
+
try {
|
|
4472
|
+
const envConfig = ENVIRONMENT_CONFIGS[this.options.sdkConfig.environment];
|
|
4473
|
+
const walletApiUrl = envConfig.walletApiUrl;
|
|
4474
|
+
console.log(`[${this.constructor.name}] Refreshing credits from:`, walletApiUrl);
|
|
4475
|
+
const creditDetail = await getCreditDetail(walletApiUrl, this.options.sdkConfig.accountToken);
|
|
4476
|
+
if (creditDetail) {
|
|
4477
|
+
console.log(`[${this.constructor.name}] Credits refreshed, total balance:`, creditDetail.total_balance);
|
|
4478
|
+
}
|
|
4479
|
+
else {
|
|
4480
|
+
console.warn(`[${this.constructor.name}] Failed to refresh credits`);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
catch (error) {
|
|
4484
|
+
console.error(`[${this.constructor.name}] Failed to refresh credits:`, error);
|
|
4485
|
+
}
|
|
4486
|
+
})();
|
|
4487
|
+
// Trigger user callback
|
|
4488
|
+
this.options.onPaymentSuccess?.(orderId, payload.transaction_id, pkg);
|
|
4489
|
+
},
|
|
4490
|
+
});
|
|
4491
|
+
successModal.open();
|
|
4492
|
+
},
|
|
4493
|
+
onFailed: (payload) => {
|
|
4494
|
+
console.error(`[${this.constructor.name}] Payment failed:`, payload);
|
|
4495
|
+
const error = new Error(payload.message || 'Payment failed');
|
|
4496
|
+
this.options.onPaymentFailed?.(error, pkg);
|
|
4497
|
+
},
|
|
4498
|
+
onError: (payload, error) => {
|
|
4499
|
+
console.error(`[${this.constructor.name}] Payment error:`, error);
|
|
4500
|
+
this.options.onPaymentFailed?.(error, pkg);
|
|
4501
|
+
},
|
|
4502
|
+
});
|
|
4503
|
+
await dropinModal.open();
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Attach event listeners to package buttons
|
|
3583
4507
|
*/
|
|
3584
|
-
attachEventListeners() {
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
4508
|
+
attachEventListeners(container) {
|
|
4509
|
+
const packages = this.getPackages();
|
|
4510
|
+
packages.forEach(pkg => {
|
|
4511
|
+
const button = container.querySelector(`[data-package-button="${pkg.id}"]`);
|
|
4512
|
+
if (button) {
|
|
4513
|
+
button.addEventListener('click', async (e) => {
|
|
4514
|
+
e.preventDefault();
|
|
4515
|
+
e.stopPropagation();
|
|
4516
|
+
console.log(`[${this.constructor.name}] Package selected:`, pkg.id);
|
|
4517
|
+
const originalText = button.innerHTML;
|
|
4518
|
+
const originalDisabled = button.disabled;
|
|
4519
|
+
try {
|
|
4520
|
+
// Disable button, show initializing state
|
|
4521
|
+
button.disabled = true;
|
|
4522
|
+
const isZh = this.language === 'zh-CN';
|
|
4523
|
+
button.innerHTML = this.getLoadingButtonHTML(isZh ? '初始化中...' : 'Initializing...');
|
|
4524
|
+
// Wait for SDK initialization (with retry)
|
|
4525
|
+
const initialized = await this.waitForSDKInitialization(30000, 1);
|
|
4526
|
+
if (!initialized) {
|
|
4527
|
+
throw new Error('SDK initialization failed or timed out. Please try again.');
|
|
4528
|
+
}
|
|
4529
|
+
// SDK initialization successful, execute payment flow
|
|
4530
|
+
await this.handlePaymentFlow(pkg, button, originalText);
|
|
4531
|
+
}
|
|
4532
|
+
catch (error) {
|
|
4533
|
+
console.error(`[${this.constructor.name}] Failed to process payment:`, error);
|
|
4534
|
+
// Restore button state
|
|
4535
|
+
button.disabled = originalDisabled;
|
|
4536
|
+
button.innerHTML = originalText;
|
|
4537
|
+
// Show error message (use custom UI instead of alert)
|
|
4538
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
4539
|
+
showErrorMessage(`Payment failed: ${errorMessage}`);
|
|
4540
|
+
// Trigger failure callback
|
|
4541
|
+
this.options.onPaymentFailed?.(error instanceof Error ? error : new Error(String(error)), pkg);
|
|
4542
|
+
}
|
|
4543
|
+
});
|
|
3591
4544
|
}
|
|
3592
4545
|
});
|
|
3593
|
-
// 点击关闭按钮
|
|
3594
|
-
const closeBtn = document.getElementById('success-modal-close-btn');
|
|
3595
|
-
if (closeBtn) {
|
|
3596
|
-
closeBtn.addEventListener('click', () => this.close());
|
|
3597
|
-
}
|
|
3598
|
-
// ESC 键关闭
|
|
3599
|
-
this.boundHandleEscKey = this.handleEscKey.bind(this);
|
|
3600
|
-
document.addEventListener('keydown', this.boundHandleEscKey);
|
|
3601
|
-
}
|
|
3602
|
-
/**
|
|
3603
|
-
* 处理 ESC 键
|
|
3604
|
-
*/
|
|
3605
|
-
handleEscKey(e) {
|
|
3606
|
-
if (e.key === 'Escape') {
|
|
3607
|
-
this.close();
|
|
3608
|
-
}
|
|
3609
4546
|
}
|
|
3610
4547
|
/**
|
|
3611
|
-
*
|
|
4548
|
+
* Cleanup resources
|
|
3612
4549
|
*/
|
|
3613
4550
|
cleanup() {
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
this.
|
|
3618
|
-
|
|
3619
|
-
// 移除元素
|
|
3620
|
-
if (this.overlay && this.overlay.parentNode) {
|
|
3621
|
-
this.overlay.parentNode.removeChild(this.overlay);
|
|
4551
|
+
console.log(`[${this.constructor.name}] Cleaning up...`);
|
|
4552
|
+
// Remove resize listener
|
|
4553
|
+
if (this.resizeHandler) {
|
|
4554
|
+
window.removeEventListener('resize', this.resizeHandler);
|
|
4555
|
+
this.resizeHandler = null;
|
|
3622
4556
|
}
|
|
3623
|
-
// 恢复 body 滚动
|
|
3624
|
-
document.body.style.position = '';
|
|
3625
|
-
document.body.style.top = '';
|
|
3626
|
-
document.body.style.width = '';
|
|
3627
|
-
window.scrollTo(0, this.scrollY);
|
|
3628
|
-
// 清空引用
|
|
3629
|
-
this.overlay = null;
|
|
3630
|
-
this.modal = null;
|
|
3631
|
-
this.isExiting = false;
|
|
3632
4557
|
}
|
|
4558
|
+
// === Utility Methods ===
|
|
3633
4559
|
/**
|
|
3634
|
-
*
|
|
3635
|
-
* 防止 XSS 攻击
|
|
4560
|
+
* Format number with commas
|
|
3636
4561
|
*/
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
div.textContent = text;
|
|
3640
|
-
return div.innerHTML;
|
|
4562
|
+
formatNumber(num) {
|
|
4563
|
+
return parseInt(num).toLocaleString();
|
|
3641
4564
|
}
|
|
3642
4565
|
/**
|
|
3643
|
-
*
|
|
4566
|
+
* Get content container from modal
|
|
3644
4567
|
*/
|
|
3645
|
-
|
|
3646
|
-
return this.
|
|
4568
|
+
getContentContainer() {
|
|
4569
|
+
return this.modal.getContentContainer();
|
|
3647
4570
|
}
|
|
3648
4571
|
}
|
|
3649
4572
|
|
|
@@ -3651,12 +4574,10 @@ class PurchaseSuccessModal {
|
|
|
3651
4574
|
* CreditPackageModal - 积分套餐选择弹框
|
|
3652
4575
|
* 展示不同的积分套餐供用户选择
|
|
3653
4576
|
*/
|
|
3654
|
-
class CreditPackageModal {
|
|
3655
|
-
constructor(
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
this.sdkInitialized = false;
|
|
3659
|
-
// 设计系统常量
|
|
4577
|
+
class CreditPackageModal extends BasePackageModal {
|
|
4578
|
+
constructor() {
|
|
4579
|
+
super(...arguments);
|
|
4580
|
+
// Design system constants
|
|
3660
4581
|
this.SPACING = {
|
|
3661
4582
|
xs: '8px',
|
|
3662
4583
|
sm: '16px',
|
|
@@ -3676,241 +4597,105 @@ class CreditPackageModal {
|
|
|
3676
4597
|
accent: '#22ce9c',
|
|
3677
4598
|
},
|
|
3678
4599
|
};
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
4600
|
+
}
|
|
4601
|
+
// === Abstract Method Implementations ===
|
|
4602
|
+
/**
|
|
4603
|
+
* Create and configure the PaymentModal instance
|
|
4604
|
+
*/
|
|
4605
|
+
createModal() {
|
|
4606
|
+
return new PaymentModal({
|
|
3683
4607
|
title: this.language === 'zh-CN'
|
|
3684
|
-
? (options.title_cn || '选择您的创作力量')
|
|
3685
|
-
: (options.title || 'Choose Your Creative Power'),
|
|
4608
|
+
? (this.options.title_cn || '选择您的创作力量')
|
|
4609
|
+
: (this.options.title || 'Choose Your Creative Power'),
|
|
3686
4610
|
showCloseButton: true,
|
|
3687
|
-
closeOnOverlayClick: false, //
|
|
3688
|
-
closeOnEsc: false, //
|
|
4611
|
+
closeOnOverlayClick: false, // Disable click overlay to close
|
|
4612
|
+
closeOnEsc: false, // Disable ESC key to close
|
|
3689
4613
|
maxWidth: '1200px',
|
|
3690
4614
|
onClose: () => {
|
|
3691
4615
|
this.cleanup();
|
|
3692
|
-
options.onClose?.();
|
|
4616
|
+
this.options.onClose?.();
|
|
3693
4617
|
},
|
|
3694
4618
|
});
|
|
3695
|
-
console.log('[CreditPackageModal] Created');
|
|
3696
|
-
}
|
|
3697
|
-
/**
|
|
3698
|
-
* 打开弹框
|
|
3699
|
-
*/
|
|
3700
|
-
async open() {
|
|
3701
|
-
console.log('[CreditPackageModal] Opening modal...');
|
|
3702
|
-
this.modal.open();
|
|
3703
|
-
// 修改弹框背景为深色
|
|
3704
|
-
const modalElement = document.querySelector('.payment-modal');
|
|
3705
|
-
if (modalElement) {
|
|
3706
|
-
modalElement.style.background = '#0a0a0f';
|
|
3707
|
-
modalElement.style.border = '1px solid rgba(255, 255, 255, 0.1)';
|
|
3708
|
-
}
|
|
3709
|
-
// 修改标题样式
|
|
3710
|
-
const titleElement = document.querySelector('.payment-modal-title');
|
|
3711
|
-
if (titleElement) {
|
|
3712
|
-
titleElement.style.color = 'white';
|
|
3713
|
-
titleElement.style.fontSize = '28px';
|
|
3714
|
-
titleElement.style.fontWeight = '700';
|
|
3715
|
-
titleElement.style.padding = '32px 32px 0 32px';
|
|
3716
|
-
titleElement.style.marginBottom = '16px';
|
|
3717
|
-
titleElement.style.letterSpacing = '-0.01em';
|
|
3718
|
-
}
|
|
3719
|
-
// 修改关闭按钮颜色并添加 hover 动画
|
|
3720
|
-
const closeButton = document.querySelector('.payment-modal-close');
|
|
3721
|
-
if (closeButton) {
|
|
3722
|
-
closeButton.style.background = 'rgba(255, 255, 255, 0.05)';
|
|
3723
|
-
closeButton.style.color = 'rgba(255, 255, 255, 0.7)';
|
|
3724
|
-
closeButton.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
3725
|
-
// 移除旧的事件监听器(如果有)
|
|
3726
|
-
const newCloseButton = closeButton.cloneNode(true);
|
|
3727
|
-
closeButton.parentNode?.replaceChild(newCloseButton, closeButton);
|
|
3728
|
-
// 添加 hover 动画
|
|
3729
|
-
newCloseButton.addEventListener('mouseenter', () => {
|
|
3730
|
-
newCloseButton.style.background = 'rgba(255, 255, 255, 0.1)';
|
|
3731
|
-
newCloseButton.style.color = 'white';
|
|
3732
|
-
newCloseButton.style.transform = 'rotate(90deg) scale(1.1)';
|
|
3733
|
-
});
|
|
3734
|
-
newCloseButton.addEventListener('mouseleave', () => {
|
|
3735
|
-
newCloseButton.style.background = 'rgba(255, 255, 255, 0.05)';
|
|
3736
|
-
newCloseButton.style.color = 'rgba(255, 255, 255, 0.7)';
|
|
3737
|
-
newCloseButton.style.transform = 'rotate(0deg) scale(1)';
|
|
3738
|
-
});
|
|
3739
|
-
newCloseButton.addEventListener('click', () => this.close());
|
|
3740
|
-
}
|
|
3741
|
-
// 渲染内容
|
|
3742
|
-
this.renderContent();
|
|
3743
|
-
// 添加resize监听器,窗口大小改变时重新渲染以应用响应式样式
|
|
3744
|
-
this.resizeHandler = () => {
|
|
3745
|
-
this.renderContent();
|
|
3746
|
-
};
|
|
3747
|
-
window.addEventListener('resize', this.resizeHandler);
|
|
3748
|
-
// 如果配置了 initPaymentSDK,在后台自动初始化 SDK
|
|
3749
|
-
if (!this.sdkInitialized && !this.isInitializingSDK) {
|
|
3750
|
-
this.initializeSDK();
|
|
3751
|
-
}
|
|
3752
|
-
console.log('[CreditPackageModal] Modal opened');
|
|
3753
4619
|
}
|
|
3754
4620
|
/**
|
|
3755
|
-
*
|
|
3756
|
-
* @param timeout 超时时间(毫秒),默认 30 秒
|
|
3757
|
-
* @param maxRetries 最大重试次数,默认 1 次
|
|
3758
|
-
* @returns 是否初始化成功
|
|
4621
|
+
* Get packages to display
|
|
3759
4622
|
*/
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
// 如果已经初始化完成,直接返回
|
|
3763
|
-
if (this.sdkInitialized) {
|
|
3764
|
-
console.log('[CreditPackageModal] SDK already initialized');
|
|
3765
|
-
return true;
|
|
3766
|
-
}
|
|
3767
|
-
// 如果还没开始初始化且有配置,主动触发
|
|
3768
|
-
if (!this.isInitializingSDK && this.options.sdkConfig) {
|
|
3769
|
-
console.log('[CreditPackageModal] Starting SDK initialization...');
|
|
3770
|
-
await this.initializeSDK();
|
|
3771
|
-
if (this.sdkInitialized) {
|
|
3772
|
-
return true;
|
|
3773
|
-
}
|
|
3774
|
-
}
|
|
3775
|
-
// 等待初始化完成
|
|
3776
|
-
console.log('[CreditPackageModal] Waiting for SDK initialization...');
|
|
3777
|
-
while (Date.now() - startTime < timeout) {
|
|
3778
|
-
if (this.sdkInitialized) {
|
|
3779
|
-
console.log('[CreditPackageModal] SDK initialization completed');
|
|
3780
|
-
return true;
|
|
3781
|
-
}
|
|
3782
|
-
if (!this.isInitializingSDK) {
|
|
3783
|
-
// 初始化已结束但未成功,尝试重试
|
|
3784
|
-
if (maxRetries > 0) {
|
|
3785
|
-
console.log(`[CreditPackageModal] SDK initialization failed, retrying... (${maxRetries} retries left)`);
|
|
3786
|
-
await this.initializeSDK();
|
|
3787
|
-
return this.waitForSDKInitialization(timeout - (Date.now() - startTime), maxRetries - 1);
|
|
3788
|
-
}
|
|
3789
|
-
else {
|
|
3790
|
-
console.error('[CreditPackageModal] SDK initialization failed after all retries');
|
|
3791
|
-
return false;
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
// 等待 100ms 后重试
|
|
3795
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
3796
|
-
}
|
|
3797
|
-
// 超时
|
|
3798
|
-
console.error('[CreditPackageModal] SDK initialization timed out');
|
|
3799
|
-
return false;
|
|
4623
|
+
getPackages() {
|
|
4624
|
+
return CREDIT_PACKAGES;
|
|
3800
4625
|
}
|
|
3801
4626
|
/**
|
|
3802
|
-
*
|
|
4627
|
+
* Get package display name for payment modal title
|
|
3803
4628
|
*/
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
}
|
|
3808
|
-
if (!this.options.sdkConfig) {
|
|
3809
|
-
console.log('[CreditPackageModal] No SDK configuration provided, skipping initialization');
|
|
3810
|
-
return;
|
|
3811
|
-
}
|
|
3812
|
-
this.isInitializingSDK = true;
|
|
3813
|
-
console.log('[CreditPackageModal] Initializing payment SDK...');
|
|
3814
|
-
// 显示加载指示器
|
|
3815
|
-
const loader = showLoadingIndicator('Initializing payment system...');
|
|
3816
|
-
try {
|
|
3817
|
-
const config = this.options.sdkConfig;
|
|
3818
|
-
// 1. 从环境配置中获取基础配置
|
|
3819
|
-
const envConfig = ENVIRONMENT_CONFIGS[config.environment];
|
|
3820
|
-
// 2. 合并配置(自定义配置优先级高于环境配置)
|
|
3821
|
-
const finalConfig = {
|
|
3822
|
-
scriptUrl: config.scriptUrl || envConfig.scriptUrl,
|
|
3823
|
-
clientId: config.clientId || envConfig.clientId,
|
|
3824
|
-
orderApiUrl: config.orderApiUrl || envConfig.orderApiUrl,
|
|
3825
|
-
cssUrl: config.cssUrl || envConfig.cssUrl,
|
|
3826
|
-
};
|
|
3827
|
-
console.log('[CreditPackageModal] Using environment:', config.environment);
|
|
3828
|
-
// 3. 初始化 SeaartPaymentSDK
|
|
3829
|
-
await SeaartPaymentSDK.getInstance().init({
|
|
3830
|
-
scriptUrl: finalConfig.scriptUrl,
|
|
3831
|
-
clientId: finalConfig.clientId,
|
|
3832
|
-
language: 'en',
|
|
3833
|
-
scriptTimeout: config.scriptTimeout,
|
|
3834
|
-
cssUrl: finalConfig.cssUrl,
|
|
3835
|
-
});
|
|
3836
|
-
// 4. 获取支付方式列表
|
|
3837
|
-
const paymentMethods = await SeaartPaymentSDK.getInstance().getPaymentMethods({
|
|
3838
|
-
country_code: config.countryCode,
|
|
3839
|
-
business_type: config.businessType ?? 1, // 默认为 1(一次性购买)
|
|
3840
|
-
});
|
|
3841
|
-
// 5. 查找匹配的支付方式
|
|
3842
|
-
const paymentMethod = config.paymentMethodType
|
|
3843
|
-
? paymentMethods.find((m) => m.payment_method_type === config.paymentMethodType ||
|
|
3844
|
-
m.payment_method_name.toLowerCase().includes(config.paymentMethodType.toLowerCase()))
|
|
3845
|
-
: paymentMethods.find((m) => m.payment_type === 2); // 默认使用 dropin (payment_type === 2)
|
|
3846
|
-
if (!paymentMethod) {
|
|
3847
|
-
throw new Error(`Payment method "${config.paymentMethodType || 'dropin'}" not found`);
|
|
3848
|
-
}
|
|
3849
|
-
// 6. 存储到类成员变量(包括 finalConfig 以供后续使用)
|
|
3850
|
-
this.options.paymentMethod = paymentMethod;
|
|
3851
|
-
this.options.accountToken = config.accountToken;
|
|
3852
|
-
this.sdkInitialized = true;
|
|
3853
|
-
// 存储最终配置到 config 对象(用于 handlePaymentFlow)
|
|
3854
|
-
this.options.sdkConfig._resolvedOrderApiUrl = finalConfig.orderApiUrl;
|
|
3855
|
-
console.log('[CreditPackageModal] SDK initialized with environment config:', {
|
|
3856
|
-
environment: config.environment,
|
|
3857
|
-
paymentMethod: paymentMethod.payment_method_name,
|
|
3858
|
-
accountToken: config.accountToken ? 'provided' : 'not provided',
|
|
3859
|
-
});
|
|
3860
|
-
}
|
|
3861
|
-
catch (error) {
|
|
3862
|
-
console.error('[CreditPackageModal] Failed to initialize payment SDK:', error);
|
|
3863
|
-
// SDK 初始化失败不影响浏览积分包,只是无法进行支付
|
|
3864
|
-
}
|
|
3865
|
-
finally {
|
|
3866
|
-
this.isInitializingSDK = false;
|
|
3867
|
-
// 隐藏加载指示器
|
|
3868
|
-
hideLoadingIndicator(loader);
|
|
3869
|
-
}
|
|
4629
|
+
getPackageDisplayName(pkg) {
|
|
4630
|
+
const isZh = this.language === 'zh-CN';
|
|
4631
|
+
return isZh ? `${pkg.credits} 积分套餐` : `${pkg.credits} Credits Package`;
|
|
3870
4632
|
}
|
|
3871
4633
|
/**
|
|
3872
|
-
*
|
|
4634
|
+
* Get loading button HTML with spinner
|
|
3873
4635
|
*/
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
4636
|
+
getLoadingButtonHTML(text, isPopular = false) {
|
|
4637
|
+
return `
|
|
4638
|
+
<svg style="
|
|
4639
|
+
display: inline-block;
|
|
4640
|
+
width: 16px;
|
|
4641
|
+
height: 16px;
|
|
4642
|
+
border: 2px solid ${isPopular ? 'rgba(10, 10, 15, 0.3)' : 'rgba(255, 255, 255, 0.3)'};
|
|
4643
|
+
border-top-color: ${isPopular ? '#0a0a0f' : 'white'};
|
|
4644
|
+
border-radius: 50%;
|
|
4645
|
+
animation: spin 0.6s linear infinite;
|
|
4646
|
+
" viewBox="0 0 24 24"></svg>
|
|
4647
|
+
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
|
4648
|
+
${text ? `<span style="margin-left: 8px;">${text}</span>` : ''}
|
|
4649
|
+
`;
|
|
3877
4650
|
}
|
|
3878
4651
|
/**
|
|
3879
|
-
*
|
|
4652
|
+
* Apply modal styling (hook method override)
|
|
3880
4653
|
*/
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
const
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
let padding = '0 60px 60px';
|
|
3888
|
-
if (isMobile) {
|
|
3889
|
-
computeColumns = 1;
|
|
3890
|
-
packColumns = 1;
|
|
3891
|
-
padding = '0 20px 20px';
|
|
4654
|
+
applyModalStyling() {
|
|
4655
|
+
// Modify modal background to dark
|
|
4656
|
+
const modalElement = document.querySelector('.payment-modal');
|
|
4657
|
+
if (modalElement) {
|
|
4658
|
+
modalElement.style.background = '#0a0a0f';
|
|
4659
|
+
modalElement.style.border = '1px solid rgba(255, 255, 255, 0.1)';
|
|
3892
4660
|
}
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
4661
|
+
// Modify title style
|
|
4662
|
+
const titleElement = document.querySelector('.payment-modal-title');
|
|
4663
|
+
if (titleElement) {
|
|
4664
|
+
titleElement.style.color = 'white';
|
|
4665
|
+
titleElement.style.fontSize = '28px';
|
|
4666
|
+
titleElement.style.fontWeight = '700';
|
|
4667
|
+
titleElement.style.padding = '32px 32px 0 32px';
|
|
4668
|
+
titleElement.style.marginBottom = '16px';
|
|
4669
|
+
titleElement.style.letterSpacing = '-0.01em';
|
|
3897
4670
|
}
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4671
|
+
// Modify close button color and add hover animation
|
|
4672
|
+
const closeButton = document.querySelector('.payment-modal-close');
|
|
4673
|
+
if (closeButton) {
|
|
4674
|
+
closeButton.style.background = 'rgba(255, 255, 255, 0.05)';
|
|
4675
|
+
closeButton.style.color = 'rgba(255, 255, 255, 0.7)';
|
|
4676
|
+
closeButton.style.transition = 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)';
|
|
4677
|
+
// Remove old event listeners (if any)
|
|
4678
|
+
const newCloseButton = closeButton.cloneNode(true);
|
|
4679
|
+
closeButton.parentNode?.replaceChild(newCloseButton, closeButton);
|
|
4680
|
+
// Add hover animation
|
|
4681
|
+
newCloseButton.addEventListener('mouseenter', () => {
|
|
4682
|
+
newCloseButton.style.background = 'rgba(255, 255, 255, 0.1)';
|
|
4683
|
+
newCloseButton.style.color = 'white';
|
|
4684
|
+
newCloseButton.style.transform = 'rotate(90deg) scale(1.1)';
|
|
4685
|
+
});
|
|
4686
|
+
newCloseButton.addEventListener('mouseleave', () => {
|
|
4687
|
+
newCloseButton.style.background = 'rgba(255, 255, 255, 0.05)';
|
|
4688
|
+
newCloseButton.style.color = 'rgba(255, 255, 255, 0.7)';
|
|
4689
|
+
newCloseButton.style.transform = 'rotate(0deg) scale(1)';
|
|
4690
|
+
});
|
|
4691
|
+
newCloseButton.addEventListener('click', () => this.close());
|
|
3902
4692
|
}
|
|
3903
|
-
return {
|
|
3904
|
-
containerPadding: padding,
|
|
3905
|
-
computeGridColumns: `repeat(${computeColumns}, 1fr)`,
|
|
3906
|
-
packGridColumns: `repeat(${packColumns}, 1fr)`,
|
|
3907
|
-
};
|
|
3908
4693
|
}
|
|
3909
4694
|
/**
|
|
3910
|
-
*
|
|
4695
|
+
* Render modal content
|
|
3911
4696
|
*/
|
|
3912
4697
|
renderContent() {
|
|
3913
|
-
const container = this.
|
|
4698
|
+
const container = this.getContentContainer();
|
|
3914
4699
|
if (!container) {
|
|
3915
4700
|
throw new Error('Modal content container not found');
|
|
3916
4701
|
}
|
|
@@ -3923,7 +4708,7 @@ class CreditPackageModal {
|
|
|
3923
4708
|
color: white;
|
|
3924
4709
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
3925
4710
|
">
|
|
3926
|
-
<!--
|
|
4711
|
+
<!-- Subtitle -->
|
|
3927
4712
|
<p style="
|
|
3928
4713
|
text-align: center;
|
|
3929
4714
|
font-size: 16px;
|
|
@@ -3933,11 +4718,11 @@ class CreditPackageModal {
|
|
|
3933
4718
|
font-weight: 400;
|
|
3934
4719
|
">
|
|
3935
4720
|
${isZh
|
|
3936
|
-
? (this.options.subtitle_cn || '
|
|
4721
|
+
? (this.options.subtitle_cn || '免费开始,随创作扩展。所有套餐都包含用于电影、游戏、音乐和世界的算力积分。')
|
|
3937
4722
|
: (this.options.subtitle || 'Start free, scale with creation. All packages include credits for movies, games, music, and worlds.')}
|
|
3938
4723
|
</p>
|
|
3939
4724
|
|
|
3940
|
-
<!--
|
|
4725
|
+
<!-- Compute credits section - fully replicate next-meta pricing -->
|
|
3941
4726
|
<div style="margin-bottom: ${this.SPACING.xl};">
|
|
3942
4727
|
<!-- Section Header -->
|
|
3943
4728
|
<div style="margin-bottom: ${this.SPACING.xl}; text-align: center;">
|
|
@@ -3961,221 +4746,18 @@ class CreditPackageModal {
|
|
|
3961
4746
|
</p>
|
|
3962
4747
|
</div>
|
|
3963
4748
|
|
|
3964
|
-
<!-- Credits Grid -
|
|
4749
|
+
<!-- Credits Grid - fully replicate next-meta 5 cards -->
|
|
3965
4750
|
<div style="
|
|
3966
4751
|
display: grid;
|
|
3967
4752
|
grid-template-columns: ${styles.computeGridColumns};
|
|
3968
4753
|
gap: 12px;
|
|
3969
4754
|
margin-bottom: 20px;
|
|
3970
4755
|
">
|
|
3971
|
-
|
|
3972
|
-
<article style="
|
|
3973
|
-
position: relative;
|
|
3974
|
-
overflow: hidden;
|
|
3975
|
-
border-radius: 16px;
|
|
3976
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
3977
|
-
background: rgba(255, 255, 255, 0.05);
|
|
3978
|
-
padding: 20px;
|
|
3979
|
-
text-align: center;
|
|
3980
|
-
backdrop-filter: blur(40px);
|
|
3981
|
-
opacity: 0.7;
|
|
3982
|
-
transform: scale(0.98);
|
|
3983
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
3984
|
-
" onmouseover="this.style.transform='translateY(-4px) scale(1)'; this.style.opacity='1'; this.style.borderColor='rgba(255, 255, 255, 0.12)'; this.style.boxShadow='0 20px 60px rgba(0,0,0,0.4)';"
|
|
3985
|
-
onmouseout="this.style.transform='scale(0.98)'; this.style.opacity='0.7'; this.style.borderColor='rgba(255, 255, 255, 0.06)'; this.style.boxShadow='none';">
|
|
3986
|
-
<div style="
|
|
3987
|
-
margin: 0 auto 16px;
|
|
3988
|
-
display: flex;
|
|
3989
|
-
align-items: center;
|
|
3990
|
-
justify-content: center;
|
|
3991
|
-
height: 48px;
|
|
3992
|
-
width: 48px;
|
|
3993
|
-
border-radius: 12px;
|
|
3994
|
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
3995
|
-
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
3996
|
-
">
|
|
3997
|
-
<svg style="height: 24px; width: 24px; color: white;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
3998
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
|
3999
|
-
</svg>
|
|
4000
|
-
</div>
|
|
4001
|
-
<h3 style="margin-bottom: 8px; font-size: 16px; font-weight: 600; color: white;">
|
|
4002
|
-
${isZh ? '电影片段' : 'Film Clips'}
|
|
4003
|
-
</h3>
|
|
4004
|
-
<p style="margin-bottom: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 20px; font-weight: 800; color: #00ff88;">
|
|
4005
|
-
100-300
|
|
4006
|
-
</p>
|
|
4007
|
-
<p style="font-size: 12px; line-height: 1.5; color: rgba(255, 255, 255, 0.5);">
|
|
4008
|
-
${isZh ? '每次生成' : 'per generation'}
|
|
4009
|
-
</p>
|
|
4010
|
-
</article>
|
|
4011
|
-
|
|
4012
|
-
<!-- Game Scenes -->
|
|
4013
|
-
<article style="
|
|
4014
|
-
position: relative;
|
|
4015
|
-
overflow: hidden;
|
|
4016
|
-
border-radius: 16px;
|
|
4017
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
4018
|
-
background: rgba(255, 255, 255, 0.05);
|
|
4019
|
-
padding: 20px;
|
|
4020
|
-
text-align: center;
|
|
4021
|
-
backdrop-filter: blur(40px);
|
|
4022
|
-
opacity: 0.7;
|
|
4023
|
-
transform: scale(0.98);
|
|
4024
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4025
|
-
" onmouseover="this.style.transform='translateY(-4px) scale(1)'; this.style.opacity='1'; this.style.borderColor='rgba(255, 255, 255, 0.12)'; this.style.boxShadow='0 20px 60px rgba(0,0,0,0.4)';"
|
|
4026
|
-
onmouseout="this.style.transform='scale(0.98)'; this.style.opacity='0.7'; this.style.borderColor='rgba(255, 255, 255, 0.06)'; this.style.boxShadow='none';">
|
|
4027
|
-
<div style="
|
|
4028
|
-
margin: 0 auto 16px;
|
|
4029
|
-
display: flex;
|
|
4030
|
-
align-items: center;
|
|
4031
|
-
justify-content: center;
|
|
4032
|
-
height: 48px;
|
|
4033
|
-
width: 48px;
|
|
4034
|
-
border-radius: 12px;
|
|
4035
|
-
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
4036
|
-
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
4037
|
-
">
|
|
4038
|
-
<svg style="height: 24px; width: 24px; color: white;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
4039
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/>
|
|
4040
|
-
</svg>
|
|
4041
|
-
</div>
|
|
4042
|
-
<h3 style="margin-bottom: 8px; font-size: 16px; font-weight: 600; color: white;">
|
|
4043
|
-
${isZh ? '游戏场景' : 'Game Scenes'}
|
|
4044
|
-
</h3>
|
|
4045
|
-
<p style="margin-bottom: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 20px; font-weight: 800; color: #00ff88;">
|
|
4046
|
-
50-200
|
|
4047
|
-
</p>
|
|
4048
|
-
<p style="font-size: 12px; line-height: 1.5; color: rgba(255, 255, 255, 0.5);">
|
|
4049
|
-
${isZh ? '每个场景' : 'per scene'}
|
|
4050
|
-
</p>
|
|
4051
|
-
</article>
|
|
4052
|
-
|
|
4053
|
-
<!-- Music -->
|
|
4054
|
-
<article style="
|
|
4055
|
-
position: relative;
|
|
4056
|
-
overflow: hidden;
|
|
4057
|
-
border-radius: 16px;
|
|
4058
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
4059
|
-
background: rgba(255, 255, 255, 0.05);
|
|
4060
|
-
padding: 20px;
|
|
4061
|
-
text-align: center;
|
|
4062
|
-
backdrop-filter: blur(40px);
|
|
4063
|
-
opacity: 0.7;
|
|
4064
|
-
transform: scale(0.98);
|
|
4065
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4066
|
-
" onmouseover="this.style.transform='translateY(-4px) scale(1)'; this.style.opacity='1'; this.style.borderColor='rgba(255, 255, 255, 0.12)'; this.style.boxShadow='0 20px 60px rgba(0,0,0,0.4)';"
|
|
4067
|
-
onmouseout="this.style.transform='scale(0.98)'; this.style.opacity='0.7'; this.style.borderColor='rgba(255, 255, 255, 0.06)'; this.style.boxShadow='none';">
|
|
4068
|
-
<div style="
|
|
4069
|
-
margin: 0 auto 16px;
|
|
4070
|
-
display: flex;
|
|
4071
|
-
align-items: center;
|
|
4072
|
-
justify-content: center;
|
|
4073
|
-
height: 48px;
|
|
4074
|
-
width: 48px;
|
|
4075
|
-
border-radius: 12px;
|
|
4076
|
-
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
4077
|
-
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
4078
|
-
">
|
|
4079
|
-
<svg style="height: 24px; width: 24px; color: white;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
4080
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"/>
|
|
4081
|
-
</svg>
|
|
4082
|
-
</div>
|
|
4083
|
-
<h3 style="margin-bottom: 8px; font-size: 16px; font-weight: 600; color: white;">
|
|
4084
|
-
${isZh ? '音乐' : 'Music'}
|
|
4085
|
-
</h3>
|
|
4086
|
-
<p style="margin-bottom: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 20px; font-weight: 800; color: #00ff88;">
|
|
4087
|
-
30-100
|
|
4088
|
-
</p>
|
|
4089
|
-
<p style="font-size: 12px; line-height: 1.5; color: rgba(255, 255, 255, 0.5);">
|
|
4090
|
-
${isZh ? '每首曲目' : 'per track'}
|
|
4091
|
-
</p>
|
|
4092
|
-
</article>
|
|
4093
|
-
|
|
4094
|
-
<!-- 3D Worlds -->
|
|
4095
|
-
<article style="
|
|
4096
|
-
position: relative;
|
|
4097
|
-
overflow: hidden;
|
|
4098
|
-
border-radius: 16px;
|
|
4099
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
4100
|
-
background: rgba(255, 255, 255, 0.05);
|
|
4101
|
-
padding: 20px;
|
|
4102
|
-
text-align: center;
|
|
4103
|
-
backdrop-filter: blur(40px);
|
|
4104
|
-
opacity: 0.7;
|
|
4105
|
-
transform: scale(0.98);
|
|
4106
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4107
|
-
" onmouseover="this.style.transform='translateY(-4px) scale(1)'; this.style.opacity='1'; this.style.borderColor='rgba(255, 255, 255, 0.12)'; this.style.boxShadow='0 20px 60px rgba(0,0,0,0.4)';"
|
|
4108
|
-
onmouseout="this.style.transform='scale(0.98)'; this.style.opacity='0.7'; this.style.borderColor='rgba(255, 255, 255, 0.06)'; this.style.boxShadow='none';">
|
|
4109
|
-
<div style="
|
|
4110
|
-
margin: 0 auto 16px;
|
|
4111
|
-
display: flex;
|
|
4112
|
-
align-items: center;
|
|
4113
|
-
justify-content: center;
|
|
4114
|
-
height: 48px;
|
|
4115
|
-
width: 48px;
|
|
4116
|
-
border-radius: 12px;
|
|
4117
|
-
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
4118
|
-
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
4119
|
-
">
|
|
4120
|
-
<svg style="height: 24px; width: 24px; color: white;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
4121
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
4122
|
-
</svg>
|
|
4123
|
-
</div>
|
|
4124
|
-
<h3 style="margin-bottom: 8px; font-size: 16px; font-weight: 600; color: white;">
|
|
4125
|
-
${isZh ? '3D 世界' : '3D Worlds'}
|
|
4126
|
-
</h3>
|
|
4127
|
-
<p style="margin-bottom: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 20px; font-weight: 800; color: #00ff88;">
|
|
4128
|
-
150-500
|
|
4129
|
-
</p>
|
|
4130
|
-
<p style="font-size: 12px; line-height: 1.5; color: rgba(255, 255, 255, 0.5);">
|
|
4131
|
-
${isZh ? '每个世界' : 'per world'}
|
|
4132
|
-
</p>
|
|
4133
|
-
</article>
|
|
4134
|
-
|
|
4135
|
-
<!-- Agent Sessions -->
|
|
4136
|
-
<article style="
|
|
4137
|
-
position: relative;
|
|
4138
|
-
overflow: hidden;
|
|
4139
|
-
border-radius: 16px;
|
|
4140
|
-
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
4141
|
-
background: rgba(255, 255, 255, 0.05);
|
|
4142
|
-
padding: 20px;
|
|
4143
|
-
text-align: center;
|
|
4144
|
-
backdrop-filter: blur(40px);
|
|
4145
|
-
opacity: 0.7;
|
|
4146
|
-
transform: scale(0.98);
|
|
4147
|
-
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4148
|
-
" onmouseover="this.style.transform='translateY(-4px) scale(1)'; this.style.opacity='1'; this.style.borderColor='rgba(255, 255, 255, 0.12)'; this.style.boxShadow='0 20px 60px rgba(0,0,0,0.4)';"
|
|
4149
|
-
onmouseout="this.style.transform='scale(0.98)'; this.style.opacity='0.7'; this.style.borderColor='rgba(255, 255, 255, 0.06)'; this.style.boxShadow='none';">
|
|
4150
|
-
<div style="
|
|
4151
|
-
margin: 0 auto 16px;
|
|
4152
|
-
display: flex;
|
|
4153
|
-
align-items: center;
|
|
4154
|
-
justify-content: center;
|
|
4155
|
-
height: 48px;
|
|
4156
|
-
width: 48px;
|
|
4157
|
-
border-radius: 12px;
|
|
4158
|
-
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
|
4159
|
-
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
4160
|
-
">
|
|
4161
|
-
<svg style="height: 24px; width: 24px; color: white;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
4162
|
-
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
|
|
4163
|
-
</svg>
|
|
4164
|
-
</div>
|
|
4165
|
-
<h3 style="margin-bottom: 8px; font-size: 16px; font-weight: 600; color: white;">
|
|
4166
|
-
${isZh ? 'AI 会话' : 'Agent Sessions'}
|
|
4167
|
-
</h3>
|
|
4168
|
-
<p style="margin-bottom: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 20px; font-weight: 800; color: #00ff88;">
|
|
4169
|
-
10
|
|
4170
|
-
</p>
|
|
4171
|
-
<p style="font-size: 12px; line-height: 1.5; color: rgba(255, 255, 255, 0.5);">
|
|
4172
|
-
${isZh ? '每次会话' : 'per session'}
|
|
4173
|
-
</p>
|
|
4174
|
-
</article>
|
|
4756
|
+
${this.renderComputeCreditsCards()}
|
|
4175
4757
|
</div>
|
|
4176
4758
|
</div>
|
|
4177
4759
|
|
|
4178
|
-
<!-- Credit Packs -
|
|
4760
|
+
<!-- Credit Packs - credit package purchase title -->
|
|
4179
4761
|
<div style="margin: 0 auto ${this.SPACING.lg}; max-width: 80rem; text-align: center;">
|
|
4180
4762
|
<h3 style="
|
|
4181
4763
|
margin-bottom: ${this.SPACING.xs};
|
|
@@ -4198,7 +4780,7 @@ class CreditPackageModal {
|
|
|
4198
4780
|
</p>
|
|
4199
4781
|
</div>
|
|
4200
4782
|
|
|
4201
|
-
<!--
|
|
4783
|
+
<!-- Package cards -->
|
|
4202
4784
|
<div style="
|
|
4203
4785
|
display: grid;
|
|
4204
4786
|
grid-template-columns: ${styles.packGridColumns};
|
|
@@ -4208,17 +4790,133 @@ class CreditPackageModal {
|
|
|
4208
4790
|
</div>
|
|
4209
4791
|
</div>
|
|
4210
4792
|
`;
|
|
4211
|
-
//
|
|
4793
|
+
// Attach event listeners
|
|
4212
4794
|
this.attachEventListeners(container);
|
|
4213
4795
|
}
|
|
4796
|
+
// === Private Helper Methods ===
|
|
4797
|
+
/**
|
|
4798
|
+
* Get responsive style configuration
|
|
4799
|
+
*/
|
|
4800
|
+
getResponsiveStyles() {
|
|
4801
|
+
const isMobile = window.matchMedia('(max-width: 768px)').matches;
|
|
4802
|
+
const isTablet = window.matchMedia('(max-width: 1200px)').matches;
|
|
4803
|
+
const isLaptop = window.matchMedia('(max-width: 1400px)').matches;
|
|
4804
|
+
let computeColumns = 5;
|
|
4805
|
+
let packColumns = 4;
|
|
4806
|
+
let padding = '0 60px 60px';
|
|
4807
|
+
if (isMobile) {
|
|
4808
|
+
computeColumns = 1;
|
|
4809
|
+
packColumns = 1;
|
|
4810
|
+
padding = '0 20px 20px';
|
|
4811
|
+
}
|
|
4812
|
+
else if (isTablet) {
|
|
4813
|
+
computeColumns = 2;
|
|
4814
|
+
packColumns = 2;
|
|
4815
|
+
padding = '0 30px 30px';
|
|
4816
|
+
}
|
|
4817
|
+
else if (isLaptop) {
|
|
4818
|
+
computeColumns = 3;
|
|
4819
|
+
packColumns = 2;
|
|
4820
|
+
padding = '0 40px 40px';
|
|
4821
|
+
}
|
|
4822
|
+
return {
|
|
4823
|
+
containerPadding: padding,
|
|
4824
|
+
computeGridColumns: `repeat(${computeColumns}, 1fr)`,
|
|
4825
|
+
packGridColumns: `repeat(${packColumns}, 1fr)`,
|
|
4826
|
+
};
|
|
4827
|
+
}
|
|
4828
|
+
/**
|
|
4829
|
+
* Render compute credits cards
|
|
4830
|
+
*/
|
|
4831
|
+
renderComputeCreditsCards() {
|
|
4832
|
+
const isZh = this.language === 'zh-CN';
|
|
4833
|
+
const cards = [
|
|
4834
|
+
{
|
|
4835
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>',
|
|
4836
|
+
gradient: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
|
4837
|
+
title: isZh ? '电影片段' : 'Film Clips',
|
|
4838
|
+
credits: '100-300',
|
|
4839
|
+
description: isZh ? '每次生成' : 'per generation',
|
|
4840
|
+
},
|
|
4841
|
+
{
|
|
4842
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z"/>',
|
|
4843
|
+
gradient: 'linear-gradient(135deg, #f093fb 0%, #f5576c 100%)',
|
|
4844
|
+
title: isZh ? '游戏场景' : 'Game Scenes',
|
|
4845
|
+
credits: '50-200',
|
|
4846
|
+
description: isZh ? '每个场景' : 'per scene',
|
|
4847
|
+
},
|
|
4848
|
+
{
|
|
4849
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3"/>',
|
|
4850
|
+
gradient: 'linear-gradient(135deg, #4facfe 0%, #00f2fe 100%)',
|
|
4851
|
+
title: isZh ? '音乐' : 'Music',
|
|
4852
|
+
credits: '30-100',
|
|
4853
|
+
description: isZh ? '每首曲目' : 'per track',
|
|
4854
|
+
},
|
|
4855
|
+
{
|
|
4856
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3.055 11H5a2 2 0 012 2v1a2 2 0 002 2 2 2 0 012 2v2.945M8 3.935V5.5A2.5 2.5 0 0010.5 8h.5a2 2 0 012 2 2 2 0 104 0 2 2 0 012-2h1.064M15 20.488V18a2 2 0 012-2h3.064M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>',
|
|
4857
|
+
gradient: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
|
|
4858
|
+
title: isZh ? '3D 世界' : '3D Worlds',
|
|
4859
|
+
credits: '150-500',
|
|
4860
|
+
description: isZh ? '每个世界' : 'per world',
|
|
4861
|
+
},
|
|
4862
|
+
{
|
|
4863
|
+
icon: '<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>',
|
|
4864
|
+
gradient: 'linear-gradient(135deg, #fa709a 0%, #fee140 100%)',
|
|
4865
|
+
title: isZh ? 'AI 会话' : 'Agent Sessions',
|
|
4866
|
+
credits: '10',
|
|
4867
|
+
description: isZh ? '每次会话' : 'per session',
|
|
4868
|
+
},
|
|
4869
|
+
];
|
|
4870
|
+
return cards.map(card => `
|
|
4871
|
+
<article style="
|
|
4872
|
+
position: relative;
|
|
4873
|
+
overflow: hidden;
|
|
4874
|
+
border-radius: 16px;
|
|
4875
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
4876
|
+
background: rgba(255, 255, 255, 0.05);
|
|
4877
|
+
padding: 20px;
|
|
4878
|
+
text-align: center;
|
|
4879
|
+
backdrop-filter: blur(40px);
|
|
4880
|
+
opacity: 0.7;
|
|
4881
|
+
transform: scale(0.98);
|
|
4882
|
+
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
4883
|
+
" onmouseover="this.style.transform='translateY(-4px) scale(1)'; this.style.opacity='1'; this.style.borderColor='rgba(255, 255, 255, 0.12)'; this.style.boxShadow='0 20px 60px rgba(0,0,0,0.4)';"
|
|
4884
|
+
onmouseout="this.style.transform='scale(0.98)'; this.style.opacity='0.7'; this.style.borderColor='rgba(255, 255, 255, 0.06)'; this.style.boxShadow='none';">
|
|
4885
|
+
<div style="
|
|
4886
|
+
margin: 0 auto 16px;
|
|
4887
|
+
display: flex;
|
|
4888
|
+
align-items: center;
|
|
4889
|
+
justify-content: center;
|
|
4890
|
+
height: 48px;
|
|
4891
|
+
width: 48px;
|
|
4892
|
+
border-radius: 12px;
|
|
4893
|
+
background: ${card.gradient};
|
|
4894
|
+
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
|
4895
|
+
">
|
|
4896
|
+
<svg style="height: 24px; width: 24px; color: white;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
4897
|
+
${card.icon}
|
|
4898
|
+
</svg>
|
|
4899
|
+
</div>
|
|
4900
|
+
<h3 style="margin-bottom: 8px; font-size: 16px; font-weight: 600; color: white;">
|
|
4901
|
+
${card.title}
|
|
4902
|
+
</h3>
|
|
4903
|
+
<p style="margin-bottom: 8px; font-family: 'Monaco', 'Menlo', monospace; font-size: 20px; font-weight: 800; color: #00ff88;">
|
|
4904
|
+
${card.credits}
|
|
4905
|
+
</p>
|
|
4906
|
+
<p style="font-size: 12px; line-height: 1.5; color: rgba(255, 255, 255, 0.5);">
|
|
4907
|
+
${card.description}
|
|
4908
|
+
</p>
|
|
4909
|
+
</article>
|
|
4910
|
+
`).join('');
|
|
4911
|
+
}
|
|
4214
4912
|
/**
|
|
4215
|
-
*
|
|
4913
|
+
* Render package card
|
|
4216
4914
|
*/
|
|
4217
4915
|
renderPackageCard(pkg, index) {
|
|
4218
4916
|
const isZh = this.language === 'zh-CN';
|
|
4219
4917
|
const isPopular = pkg.is_popular;
|
|
4220
|
-
const hasBonus = parseInt(pkg.bonus_credits) > 0;
|
|
4221
|
-
// Popular
|
|
4918
|
+
const hasBonus = pkg.bonus_credits && parseInt(pkg.bonus_credits) > 0;
|
|
4919
|
+
// Popular package breathing animation
|
|
4222
4920
|
const pulseAnimation = isPopular ? `
|
|
4223
4921
|
<style>
|
|
4224
4922
|
@keyframes pulse-${pkg.id} {
|
|
@@ -4278,7 +4976,7 @@ class CreditPackageModal {
|
|
|
4278
4976
|
</div>
|
|
4279
4977
|
` : ''}
|
|
4280
4978
|
|
|
4281
|
-
<!--
|
|
4979
|
+
<!-- Total credits -->
|
|
4282
4980
|
<div style="
|
|
4283
4981
|
margin-bottom: 8px;
|
|
4284
4982
|
font-size: 36px;
|
|
@@ -4290,7 +4988,7 @@ class CreditPackageModal {
|
|
|
4290
4988
|
${this.formatNumber(pkg.credits)}
|
|
4291
4989
|
</div>
|
|
4292
4990
|
|
|
4293
|
-
<!--
|
|
4991
|
+
<!-- Credits breakdown -->
|
|
4294
4992
|
<div style="
|
|
4295
4993
|
margin-bottom: 12px;
|
|
4296
4994
|
display: flex;
|
|
@@ -4304,7 +5002,7 @@ class CreditPackageModal {
|
|
|
4304
5002
|
font-weight: 500;
|
|
4305
5003
|
color: hsl(240, 5%, 65%);
|
|
4306
5004
|
">
|
|
4307
|
-
${this.formatNumber(pkg.base_credits)} ${isZh ? '积分' : 'Credits'}
|
|
5005
|
+
${this.formatNumber(pkg.base_credits || pkg.credits)} ${isZh ? '积分' : 'Credits'}
|
|
4308
5006
|
</span>
|
|
4309
5007
|
${hasBonus ? `
|
|
4310
5008
|
<span style="
|
|
@@ -4320,7 +5018,7 @@ class CreditPackageModal {
|
|
|
4320
5018
|
` : ''}
|
|
4321
5019
|
</div>
|
|
4322
5020
|
|
|
4323
|
-
<!--
|
|
5021
|
+
<!-- Buy button -->
|
|
4324
5022
|
<button
|
|
4325
5023
|
data-package-button="${pkg.id}"
|
|
4326
5024
|
style="
|
|
@@ -4345,202 +5043,6 @@ class CreditPackageModal {
|
|
|
4345
5043
|
</div>
|
|
4346
5044
|
`;
|
|
4347
5045
|
}
|
|
4348
|
-
/**
|
|
4349
|
-
* 格式化数字(添加逗号)
|
|
4350
|
-
*/
|
|
4351
|
-
formatNumber(num) {
|
|
4352
|
-
return parseInt(num).toLocaleString();
|
|
4353
|
-
}
|
|
4354
|
-
/**
|
|
4355
|
-
* 获取加载按钮的 HTML(带旋转动画)
|
|
4356
|
-
* @param text 加载文本
|
|
4357
|
-
* @param isPopular 是否为 Popular 套餐(用于调整颜色)
|
|
4358
|
-
*/
|
|
4359
|
-
getLoadingButtonHTML(text, isPopular = false) {
|
|
4360
|
-
return `
|
|
4361
|
-
<svg style="
|
|
4362
|
-
display: inline-block;
|
|
4363
|
-
width: 16px;
|
|
4364
|
-
height: 16px;
|
|
4365
|
-
border: 2px solid ${isPopular ? 'rgba(10, 10, 15, 0.3)' : 'rgba(255, 255, 255, 0.3)'};
|
|
4366
|
-
border-top-color: ${isPopular ? '#0a0a0f' : 'white'};
|
|
4367
|
-
border-radius: 50%;
|
|
4368
|
-
animation: spin 0.6s linear infinite;
|
|
4369
|
-
" viewBox="0 0 24 24"></svg>
|
|
4370
|
-
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
|
4371
|
-
${text ? `<span style="margin-left: 8px;">${text}</span>` : ''}
|
|
4372
|
-
`;
|
|
4373
|
-
}
|
|
4374
|
-
/**
|
|
4375
|
-
* 添加事件监听
|
|
4376
|
-
*/
|
|
4377
|
-
attachEventListeners(container) {
|
|
4378
|
-
// 为每个套餐按钮添加点击事件
|
|
4379
|
-
CREDIT_PACKAGES.forEach(pkg => {
|
|
4380
|
-
const button = container.querySelector(`[data-package-button="${pkg.id}"]`);
|
|
4381
|
-
if (button) {
|
|
4382
|
-
button.addEventListener('click', async (e) => {
|
|
4383
|
-
e.preventDefault();
|
|
4384
|
-
e.stopPropagation();
|
|
4385
|
-
console.log('[CreditPackageModal] Package selected:', pkg.id);
|
|
4386
|
-
// 保存原始按钮文本
|
|
4387
|
-
const originalText = button.innerHTML;
|
|
4388
|
-
const originalDisabled = button.disabled;
|
|
4389
|
-
try {
|
|
4390
|
-
// 禁用按钮,显示初始化状态
|
|
4391
|
-
button.disabled = true;
|
|
4392
|
-
const isZh = this.language === 'zh-CN';
|
|
4393
|
-
button.innerHTML = this.getLoadingButtonHTML(isZh ? '初始化中...' : 'Initializing...', pkg.is_popular);
|
|
4394
|
-
// 等待 SDK 初始化(支持重试)
|
|
4395
|
-
const initialized = await this.waitForSDKInitialization(30000, 1);
|
|
4396
|
-
if (!initialized) {
|
|
4397
|
-
throw new Error('SDK initialization failed or timed out. Please try again.');
|
|
4398
|
-
}
|
|
4399
|
-
// SDK 初始化成功,执行支付流程
|
|
4400
|
-
await this.handlePaymentFlow(pkg, button, originalText);
|
|
4401
|
-
}
|
|
4402
|
-
catch (error) {
|
|
4403
|
-
console.error('[CreditPackageModal] Failed to process payment:', error);
|
|
4404
|
-
// 恢复按钮状态
|
|
4405
|
-
button.disabled = originalDisabled;
|
|
4406
|
-
button.innerHTML = originalText;
|
|
4407
|
-
// 显示错误提示(使用自定义 UI 替代 alert)
|
|
4408
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
4409
|
-
showErrorMessage(`Payment failed: ${errorMessage}`);
|
|
4410
|
-
// 触发失败回调
|
|
4411
|
-
this.options.onPaymentFailed?.(error instanceof Error ? error : new Error(String(error)));
|
|
4412
|
-
}
|
|
4413
|
-
});
|
|
4414
|
-
}
|
|
4415
|
-
});
|
|
4416
|
-
}
|
|
4417
|
-
/**
|
|
4418
|
-
* 处理支付流程
|
|
4419
|
-
*/
|
|
4420
|
-
async handlePaymentFlow(pkg, button, originalHTML) {
|
|
4421
|
-
try {
|
|
4422
|
-
// 更新按钮状态为"创建订单中"
|
|
4423
|
-
button.innerHTML = this.getLoadingButtonHTML('Creating order...', pkg.is_popular);
|
|
4424
|
-
console.log('[CreditPackageModal] Creating order for package:', pkg.id);
|
|
4425
|
-
// 使用默认实现:调用解析后的 orderApiUrl
|
|
4426
|
-
const resolvedOrderApiUrl = this.options.sdkConfig._resolvedOrderApiUrl || ENVIRONMENT_CONFIGS[this.options.sdkConfig.environment].orderApiUrl;
|
|
4427
|
-
const response = await createOrder(resolvedOrderApiUrl, this.options.sdkConfig.accountToken || '', {
|
|
4428
|
-
product_id: pkg.id,
|
|
4429
|
-
purchase_type: this.options.sdkConfig.businessType ?? 1, // 默认为 1
|
|
4430
|
-
});
|
|
4431
|
-
console.log('[CreditPackageModal] Create order response:', response);
|
|
4432
|
-
if (!response || !response.transaction_id) {
|
|
4433
|
-
throw new Error('Failed to create order: Invalid response from API');
|
|
4434
|
-
}
|
|
4435
|
-
const orderId = response.transaction_id;
|
|
4436
|
-
console.log('[CreditPackageModal] Order created:', orderId);
|
|
4437
|
-
// 恢复按钮状态
|
|
4438
|
-
button.disabled = false;
|
|
4439
|
-
button.innerHTML = originalHTML;
|
|
4440
|
-
// 创建并打开支付弹框
|
|
4441
|
-
await this.openPaymentModal(orderId, pkg);
|
|
4442
|
-
}
|
|
4443
|
-
catch (error) {
|
|
4444
|
-
console.error('[CreditPackageModal] Payment flow failed:', error);
|
|
4445
|
-
// 恢复按钮状态
|
|
4446
|
-
button.disabled = false;
|
|
4447
|
-
button.innerHTML = originalHTML;
|
|
4448
|
-
// 显示错误提示(使用自定义 UI 替代 alert)
|
|
4449
|
-
showErrorMessage(`Payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
4450
|
-
// 触发失败回调
|
|
4451
|
-
this.options.onPaymentFailed?.(error instanceof Error ? error : new Error(String(error)));
|
|
4452
|
-
}
|
|
4453
|
-
}
|
|
4454
|
-
/**
|
|
4455
|
-
* 打开支付弹框
|
|
4456
|
-
*/
|
|
4457
|
-
async openPaymentModal(orderId, pkg) {
|
|
4458
|
-
if (!this.options.paymentMethod) {
|
|
4459
|
-
throw new Error('Payment method not configured');
|
|
4460
|
-
}
|
|
4461
|
-
const isZh = this.language === 'zh-CN';
|
|
4462
|
-
// 创建支付实例(使用 orderId)
|
|
4463
|
-
let paymentInstance;
|
|
4464
|
-
if (this.options.sdkConfig || this.sdkInitialized) {
|
|
4465
|
-
// 方案A:使用 sdkConfig 初始化后,创建支付实例
|
|
4466
|
-
paymentInstance = window.SeaartPaymentComponent.createPayment({
|
|
4467
|
-
sys_order_id: orderId,
|
|
4468
|
-
account_token: this.options.accountToken,
|
|
4469
|
-
});
|
|
4470
|
-
}
|
|
4471
|
-
else {
|
|
4472
|
-
throw new Error('Payment SDK not initialized. Please provide sdkConfig or paymentSDK.');
|
|
4473
|
-
}
|
|
4474
|
-
const dropinModal = new DropinPaymentModal(paymentInstance, orderId, this.options.accountToken, this.options.paymentMethod, {
|
|
4475
|
-
modalTitle: isZh ? `购买 ${pkg.credits} 积分` : `Purchase ${pkg.credits} Credits`,
|
|
4476
|
-
onCompleted: (payload) => {
|
|
4477
|
-
console.log('[CreditPackageModal] Payment completed:', payload);
|
|
4478
|
-
this.close();
|
|
4479
|
-
// 显示购买成功弹窗
|
|
4480
|
-
const successModal = new PurchaseSuccessModal({
|
|
4481
|
-
data: {
|
|
4482
|
-
packName: isZh ? `${pkg.credits} 积分套餐` : `${pkg.credits} Credits Package`,
|
|
4483
|
-
credits: parseInt(pkg.credits),
|
|
4484
|
-
amount: pkg.price,
|
|
4485
|
-
currency: '$',
|
|
4486
|
-
orderId: orderId,
|
|
4487
|
-
transactionId: payload.transaction_id,
|
|
4488
|
-
},
|
|
4489
|
-
language: this.options.language,
|
|
4490
|
-
onClose: () => {
|
|
4491
|
-
// 弹窗关闭后刷新积分
|
|
4492
|
-
(async () => {
|
|
4493
|
-
try {
|
|
4494
|
-
const envConfig = ENVIRONMENT_CONFIGS[this.options.sdkConfig.environment];
|
|
4495
|
-
const walletApiUrl = envConfig.walletApiUrl;
|
|
4496
|
-
console.log('[CreditPackageModal] Refreshing credits from:', walletApiUrl);
|
|
4497
|
-
const creditDetail = await getCreditDetail(walletApiUrl, this.options.sdkConfig.accountToken);
|
|
4498
|
-
if (creditDetail) {
|
|
4499
|
-
console.log('[CreditPackageModal] Credits refreshed, total balance:', creditDetail.total_balance);
|
|
4500
|
-
}
|
|
4501
|
-
else {
|
|
4502
|
-
console.warn('[CreditPackageModal] Failed to refresh credits');
|
|
4503
|
-
}
|
|
4504
|
-
}
|
|
4505
|
-
catch (error) {
|
|
4506
|
-
console.error('[CreditPackageModal] Failed to refresh credits:', error);
|
|
4507
|
-
}
|
|
4508
|
-
})();
|
|
4509
|
-
// 触发用户回调
|
|
4510
|
-
this.options.onPaymentSuccess?.(orderId, payload.transaction_id);
|
|
4511
|
-
},
|
|
4512
|
-
});
|
|
4513
|
-
successModal.open();
|
|
4514
|
-
},
|
|
4515
|
-
onFailed: (payload) => {
|
|
4516
|
-
console.error('[CreditPackageModal] Payment failed:', payload);
|
|
4517
|
-
const error = new Error(payload.message || 'Payment failed');
|
|
4518
|
-
this.options.onPaymentFailed?.(error);
|
|
4519
|
-
},
|
|
4520
|
-
onError: (payload, error) => {
|
|
4521
|
-
console.error('[CreditPackageModal] Payment error:', error);
|
|
4522
|
-
this.options.onPaymentFailed?.(error);
|
|
4523
|
-
},
|
|
4524
|
-
});
|
|
4525
|
-
await dropinModal.open();
|
|
4526
|
-
}
|
|
4527
|
-
/**
|
|
4528
|
-
* 清理资源
|
|
4529
|
-
*/
|
|
4530
|
-
cleanup() {
|
|
4531
|
-
console.log('[CreditPackageModal] Cleaning up...');
|
|
4532
|
-
// 移除resize监听器
|
|
4533
|
-
if (this.resizeHandler) {
|
|
4534
|
-
window.removeEventListener('resize', this.resizeHandler);
|
|
4535
|
-
this.resizeHandler = null;
|
|
4536
|
-
}
|
|
4537
|
-
}
|
|
4538
|
-
/**
|
|
4539
|
-
* 检查弹框是否打开
|
|
4540
|
-
*/
|
|
4541
|
-
isOpen() {
|
|
4542
|
-
return this.modal.isModalOpen();
|
|
4543
|
-
}
|
|
4544
5046
|
}
|
|
4545
5047
|
|
|
4546
5048
|
/**
|
|
@@ -4548,46 +5050,75 @@ class CreditPackageModal {
|
|
|
4548
5050
|
* 支持多种套餐类型(破冰包、告急包、首充包等)
|
|
4549
5051
|
* 套餐数据从外部配置传入,无硬编码
|
|
4550
5052
|
*/
|
|
4551
|
-
class GenericPackageModal {
|
|
5053
|
+
class GenericPackageModal extends BasePackageModal {
|
|
4552
5054
|
constructor(options) {
|
|
4553
|
-
|
|
4554
|
-
this.isInitializingSDK = false;
|
|
4555
|
-
this.sdkInitialized = false;
|
|
4556
|
-
// 验证必填字段
|
|
5055
|
+
// Validate required fields
|
|
4557
5056
|
if (!options.packages || options.packages.length === 0) {
|
|
4558
5057
|
throw new Error('GenericPackageModal: packages array is required and cannot be empty');
|
|
4559
5058
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
5059
|
+
super(options);
|
|
5060
|
+
}
|
|
5061
|
+
// === Abstract Method Implementations ===
|
|
5062
|
+
/**
|
|
5063
|
+
* Create and configure the PaymentModal instance
|
|
5064
|
+
*/
|
|
5065
|
+
createModal() {
|
|
5066
|
+
// Dynamically adjust width based on package count
|
|
5067
|
+
const packageCount = this.options.packages.length;
|
|
5068
|
+
let maxWidth = '680px'; // Single package default width (optimized aspect ratio, ensure credit text is fully displayed)
|
|
4565
5069
|
if (packageCount === 2) {
|
|
4566
5070
|
maxWidth = '1100px';
|
|
4567
5071
|
}
|
|
4568
5072
|
else if (packageCount >= 3) {
|
|
4569
5073
|
maxWidth = '1200px';
|
|
4570
5074
|
}
|
|
4571
|
-
|
|
4572
|
-
title: '', //
|
|
4573
|
-
showCloseButton: false, //
|
|
4574
|
-
closeOnOverlayClick: false, //
|
|
4575
|
-
closeOnEsc: true, //
|
|
5075
|
+
return new PaymentModal({
|
|
5076
|
+
title: '', // No title
|
|
5077
|
+
showCloseButton: false, // No modal close button, use card close button
|
|
5078
|
+
closeOnOverlayClick: false, // Disable click overlay to close
|
|
5079
|
+
closeOnEsc: true, // Allow ESC key to close
|
|
4576
5080
|
maxWidth: maxWidth,
|
|
4577
5081
|
onClose: () => {
|
|
4578
5082
|
this.cleanup();
|
|
4579
|
-
options.onClose?.();
|
|
5083
|
+
this.options.onClose?.();
|
|
4580
5084
|
},
|
|
4581
5085
|
});
|
|
4582
|
-
console.log('[GenericPackageModal] Created with', options.packages.length, 'packages');
|
|
4583
5086
|
}
|
|
4584
5087
|
/**
|
|
4585
|
-
*
|
|
5088
|
+
* Get packages to display
|
|
5089
|
+
*/
|
|
5090
|
+
getPackages() {
|
|
5091
|
+
return this.options.packages;
|
|
5092
|
+
}
|
|
5093
|
+
/**
|
|
5094
|
+
* Get package display name for payment modal title
|
|
5095
|
+
*/
|
|
5096
|
+
getPackageDisplayName(pkg) {
|
|
5097
|
+
return pkg.name;
|
|
5098
|
+
}
|
|
5099
|
+
/**
|
|
5100
|
+
* Get loading button HTML with spinner
|
|
5101
|
+
*/
|
|
5102
|
+
getLoadingButtonHTML(text) {
|
|
5103
|
+
return `
|
|
5104
|
+
<svg style="
|
|
5105
|
+
display: inline-block;
|
|
5106
|
+
width: 16px;
|
|
5107
|
+
height: 16px;
|
|
5108
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
5109
|
+
border-top-color: white;
|
|
5110
|
+
border-radius: 50%;
|
|
5111
|
+
animation: spin 0.6s linear infinite;
|
|
5112
|
+
" viewBox="0 0 24 24"></svg>
|
|
5113
|
+
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
|
5114
|
+
<span style="margin-left: 8px;">${text}</span>
|
|
5115
|
+
`;
|
|
5116
|
+
}
|
|
5117
|
+
/**
|
|
5118
|
+
* Apply modal styling (hook method override)
|
|
4586
5119
|
*/
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
this.modal.open();
|
|
4590
|
-
// 修改弹框样式 - 让卡片完全填充
|
|
5120
|
+
applyModalStyling() {
|
|
5121
|
+
// Modify modal style - make card fully fill
|
|
4591
5122
|
const modalElement = document.querySelector('.payment-modal');
|
|
4592
5123
|
if (modalElement) {
|
|
4593
5124
|
modalElement.style.background = 'transparent';
|
|
@@ -4596,176 +5127,41 @@ class GenericPackageModal {
|
|
|
4596
5127
|
modalElement.style.borderRadius = '16px';
|
|
4597
5128
|
modalElement.style.overflow = 'hidden';
|
|
4598
5129
|
}
|
|
4599
|
-
//
|
|
5130
|
+
// Modify content container style - remove padding
|
|
4600
5131
|
const contentElement = document.querySelector('.payment-modal-content');
|
|
4601
5132
|
if (contentElement) {
|
|
4602
5133
|
contentElement.style.padding = '0';
|
|
4603
5134
|
contentElement.style.margin = '0';
|
|
4604
5135
|
}
|
|
4605
|
-
//
|
|
4606
|
-
const container = this.
|
|
5136
|
+
// Listen for close button event from card
|
|
5137
|
+
const container = this.getContentContainer();
|
|
4607
5138
|
if (container) {
|
|
4608
5139
|
container.addEventListener('close-modal', () => {
|
|
4609
5140
|
this.close();
|
|
4610
5141
|
});
|
|
4611
5142
|
}
|
|
4612
|
-
// 渲染内容
|
|
4613
|
-
this.renderContent();
|
|
4614
|
-
// 添加resize监听器,窗口大小改变时重新渲染以应用响应式样式
|
|
4615
|
-
this.resizeHandler = () => {
|
|
4616
|
-
this.renderContent();
|
|
4617
|
-
};
|
|
4618
|
-
window.addEventListener('resize', this.resizeHandler);
|
|
4619
|
-
// 如果配置了 sdkConfig,在后台自动初始化 SDK
|
|
4620
|
-
if (!this.sdkInitialized && !this.isInitializingSDK) {
|
|
4621
|
-
this.initializeSDK();
|
|
4622
|
-
}
|
|
4623
|
-
console.log('[GenericPackageModal] Modal opened');
|
|
4624
|
-
}
|
|
4625
|
-
/**
|
|
4626
|
-
* 等待 SDK 初始化完成(支持超时和重试)
|
|
4627
|
-
* @param timeout 超时时间(毫秒),默认 30 秒
|
|
4628
|
-
* @param maxRetries 最大重试次数,默认 1 次
|
|
4629
|
-
* @returns 是否初始化成功
|
|
4630
|
-
*/
|
|
4631
|
-
async waitForSDKInitialization(timeout = 30000, maxRetries = 1) {
|
|
4632
|
-
const startTime = Date.now();
|
|
4633
|
-
// 如果已经初始化完成,直接返回
|
|
4634
|
-
if (this.sdkInitialized) {
|
|
4635
|
-
console.log('[GenericPackageModal] SDK already initialized');
|
|
4636
|
-
return true;
|
|
4637
|
-
}
|
|
4638
|
-
// 如果还没开始初始化且有配置,主动触发
|
|
4639
|
-
if (!this.isInitializingSDK && this.options.sdkConfig) {
|
|
4640
|
-
console.log('[GenericPackageModal] Starting SDK initialization...');
|
|
4641
|
-
await this.initializeSDK();
|
|
4642
|
-
if (this.sdkInitialized) {
|
|
4643
|
-
return true;
|
|
4644
|
-
}
|
|
4645
|
-
}
|
|
4646
|
-
// 等待初始化完成
|
|
4647
|
-
console.log('[GenericPackageModal] Waiting for SDK initialization...');
|
|
4648
|
-
while (Date.now() - startTime < timeout) {
|
|
4649
|
-
if (this.sdkInitialized) {
|
|
4650
|
-
console.log('[GenericPackageModal] SDK initialization completed');
|
|
4651
|
-
return true;
|
|
4652
|
-
}
|
|
4653
|
-
if (!this.isInitializingSDK) {
|
|
4654
|
-
// 初始化已结束但未成功,尝试重试
|
|
4655
|
-
if (maxRetries > 0) {
|
|
4656
|
-
console.log(`[GenericPackageModal] SDK initialization failed, retrying... (${maxRetries} retries left)`);
|
|
4657
|
-
await this.initializeSDK();
|
|
4658
|
-
return this.waitForSDKInitialization(timeout - (Date.now() - startTime), maxRetries - 1);
|
|
4659
|
-
}
|
|
4660
|
-
else {
|
|
4661
|
-
console.error('[GenericPackageModal] SDK initialization failed after all retries');
|
|
4662
|
-
return false;
|
|
4663
|
-
}
|
|
4664
|
-
}
|
|
4665
|
-
// 等待 100ms 后重试
|
|
4666
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
4667
|
-
}
|
|
4668
|
-
// 超时
|
|
4669
|
-
console.error('[GenericPackageModal] SDK initialization timed out');
|
|
4670
|
-
return false;
|
|
4671
|
-
}
|
|
4672
|
-
/**
|
|
4673
|
-
* 初始化支付SDK(后台静默执行)
|
|
4674
|
-
*/
|
|
4675
|
-
async initializeSDK() {
|
|
4676
|
-
if (this.isInitializingSDK || this.sdkInitialized) {
|
|
4677
|
-
return;
|
|
4678
|
-
}
|
|
4679
|
-
if (!this.options.sdkConfig) {
|
|
4680
|
-
console.log('[GenericPackageModal] No SDK configuration provided, skipping initialization');
|
|
4681
|
-
return;
|
|
4682
|
-
}
|
|
4683
|
-
this.isInitializingSDK = true;
|
|
4684
|
-
console.log('[GenericPackageModal] Initializing payment SDK...');
|
|
4685
|
-
// 显示加载指示器
|
|
4686
|
-
const loader = showLoadingIndicator('Initializing payment system...');
|
|
4687
|
-
try {
|
|
4688
|
-
const config = this.options.sdkConfig;
|
|
4689
|
-
// 1. 从环境配置中获取基础配置
|
|
4690
|
-
const envConfig = ENVIRONMENT_CONFIGS[config.environment];
|
|
4691
|
-
// 2. 合并配置(自定义配置优先级高于环境配置)
|
|
4692
|
-
const finalConfig = {
|
|
4693
|
-
scriptUrl: config.scriptUrl || envConfig.scriptUrl,
|
|
4694
|
-
clientId: config.clientId || envConfig.clientId,
|
|
4695
|
-
orderApiUrl: config.orderApiUrl || envConfig.orderApiUrl,
|
|
4696
|
-
cssUrl: config.cssUrl || envConfig.cssUrl,
|
|
4697
|
-
};
|
|
4698
|
-
console.log('[GenericPackageModal] Using environment:', config.environment);
|
|
4699
|
-
// 3. 初始化 SeaartPaymentSDK
|
|
4700
|
-
await SeaartPaymentSDK.getInstance().init({
|
|
4701
|
-
scriptUrl: finalConfig.scriptUrl,
|
|
4702
|
-
clientId: finalConfig.clientId,
|
|
4703
|
-
language: 'en',
|
|
4704
|
-
scriptTimeout: config.scriptTimeout,
|
|
4705
|
-
cssUrl: finalConfig.cssUrl,
|
|
4706
|
-
});
|
|
4707
|
-
// 4. 获取支付方式列表
|
|
4708
|
-
const paymentMethods = await SeaartPaymentSDK.getInstance().getPaymentMethods({
|
|
4709
|
-
country_code: config.countryCode,
|
|
4710
|
-
business_type: config.businessType ?? 1, // 默认为 1(一次性购买)
|
|
4711
|
-
});
|
|
4712
|
-
// 5. 查找匹配的支付方式
|
|
4713
|
-
const paymentMethod = config.paymentMethodType
|
|
4714
|
-
? paymentMethods.find((m) => m.payment_method_type === config.paymentMethodType ||
|
|
4715
|
-
m.payment_method_name.toLowerCase().includes(config.paymentMethodType.toLowerCase()))
|
|
4716
|
-
: paymentMethods.find((m) => m.payment_type === 2); // 默认使用 dropin (payment_type === 2)
|
|
4717
|
-
if (!paymentMethod) {
|
|
4718
|
-
throw new Error(`Payment method "${config.paymentMethodType || 'dropin'}" not found`);
|
|
4719
|
-
}
|
|
4720
|
-
// 6. 存储到类成员变量(包括 finalConfig 以供后续使用)
|
|
4721
|
-
this.paymentMethod = paymentMethod;
|
|
4722
|
-
this.accountToken = config.accountToken;
|
|
4723
|
-
this.sdkInitialized = true;
|
|
4724
|
-
// 存储最终配置到 config 对象(用于 handlePaymentFlow)
|
|
4725
|
-
this.options.sdkConfig._resolvedOrderApiUrl = finalConfig.orderApiUrl;
|
|
4726
|
-
console.log('[GenericPackageModal] SDK initialized with environment config:', {
|
|
4727
|
-
environment: config.environment,
|
|
4728
|
-
paymentMethod: paymentMethod.payment_method_name,
|
|
4729
|
-
accountToken: config.accountToken ? 'provided' : 'not provided',
|
|
4730
|
-
});
|
|
4731
|
-
}
|
|
4732
|
-
catch (error) {
|
|
4733
|
-
console.error('[GenericPackageModal] Failed to initialize payment SDK:', error);
|
|
4734
|
-
// SDK 初始化失败不影响浏览套餐,只是无法进行支付
|
|
4735
|
-
}
|
|
4736
|
-
finally {
|
|
4737
|
-
this.isInitializingSDK = false;
|
|
4738
|
-
// 隐藏加载指示器
|
|
4739
|
-
hideLoadingIndicator(loader);
|
|
4740
|
-
}
|
|
4741
|
-
}
|
|
4742
|
-
/**
|
|
4743
|
-
* 关闭弹框
|
|
4744
|
-
*/
|
|
4745
|
-
close() {
|
|
4746
|
-
console.log('[GenericPackageModal] Closing modal...');
|
|
4747
|
-
this.modal.close();
|
|
4748
5143
|
}
|
|
4749
5144
|
/**
|
|
4750
|
-
*
|
|
5145
|
+
* Render modal content
|
|
4751
5146
|
*/
|
|
4752
5147
|
renderContent() {
|
|
4753
|
-
const container = this.
|
|
5148
|
+
const container = this.getContentContainer();
|
|
4754
5149
|
if (!container) {
|
|
4755
5150
|
throw new Error('Modal content container not found');
|
|
4756
5151
|
}
|
|
4757
|
-
//
|
|
5152
|
+
// Directly render card content without any outer wrapper
|
|
4758
5153
|
container.innerHTML = this.options.packages.map((pkg, index) => this.renderPackageCard(pkg, index)).join('');
|
|
4759
|
-
//
|
|
5154
|
+
// Attach event listeners
|
|
4760
5155
|
this.attachEventListeners(container);
|
|
4761
5156
|
}
|
|
5157
|
+
// === Private Helper Methods ===
|
|
4762
5158
|
/**
|
|
4763
|
-
*
|
|
5159
|
+
* Render package card
|
|
4764
5160
|
*/
|
|
4765
5161
|
renderPackageCard(pkg, index) {
|
|
4766
5162
|
const hasBonus = pkg.bonus_credits && parseInt(pkg.bonus_credits) > 0;
|
|
4767
5163
|
const hasBonusPercentage = pkg.bonus_percentage && pkg.bonus_percentage > 0;
|
|
4768
|
-
//
|
|
5164
|
+
// Determine title based on package type
|
|
4769
5165
|
let packageTitle = '';
|
|
4770
5166
|
if (pkg.package_type === 'iceBreaker' || pkg.package_type === 'firstCharge') {
|
|
4771
5167
|
packageTitle = 'One-time Only';
|
|
@@ -4794,7 +5190,7 @@ class GenericPackageModal {
|
|
|
4794
5190
|
onmouseover="this.style.borderColor='rgba(255, 255, 255, 0.2)';"
|
|
4795
5191
|
onmouseout="this.style.borderColor='rgba(255, 255, 255, 0.1)';"
|
|
4796
5192
|
>
|
|
4797
|
-
<!--
|
|
5193
|
+
<!-- Package title - top center of card -->
|
|
4798
5194
|
${packageTitle ? `
|
|
4799
5195
|
<div style="
|
|
4800
5196
|
position: absolute;
|
|
@@ -4812,7 +5208,7 @@ class GenericPackageModal {
|
|
|
4812
5208
|
</div>
|
|
4813
5209
|
` : ''}
|
|
4814
5210
|
|
|
4815
|
-
<!--
|
|
5211
|
+
<!-- Close button - top right -->
|
|
4816
5212
|
<button
|
|
4817
5213
|
onclick="this.closest('[data-package-id]').dispatchEvent(new CustomEvent('close-modal', { bubbles: true }));"
|
|
4818
5214
|
style="
|
|
@@ -4840,7 +5236,7 @@ class GenericPackageModal {
|
|
|
4840
5236
|
×
|
|
4841
5237
|
</button>
|
|
4842
5238
|
|
|
4843
|
-
<!--
|
|
5239
|
+
<!-- Discount tag - below close button -->
|
|
4844
5240
|
${hasBonusPercentage ? `
|
|
4845
5241
|
<div style="
|
|
4846
5242
|
position: absolute;
|
|
@@ -4860,7 +5256,7 @@ class GenericPackageModal {
|
|
|
4860
5256
|
</div>
|
|
4861
5257
|
` : ''}
|
|
4862
5258
|
|
|
4863
|
-
<!--
|
|
5259
|
+
<!-- Credits display area - ensure no line break -->
|
|
4864
5260
|
<div style="
|
|
4865
5261
|
display: flex;
|
|
4866
5262
|
flex-direction: column;
|
|
@@ -4877,7 +5273,7 @@ class GenericPackageModal {
|
|
|
4877
5273
|
gap: 12px;
|
|
4878
5274
|
flex-wrap: nowrap;
|
|
4879
5275
|
">
|
|
4880
|
-
<!--
|
|
5276
|
+
<!-- Base credits -->
|
|
4881
5277
|
<span style="
|
|
4882
5278
|
font-size: 80px;
|
|
4883
5279
|
line-height: 1;
|
|
@@ -4889,7 +5285,7 @@ class GenericPackageModal {
|
|
|
4889
5285
|
${this.formatNumber(pkg.base_credits || pkg.credits)}
|
|
4890
5286
|
</span>
|
|
4891
5287
|
|
|
4892
|
-
<!-- credits
|
|
5288
|
+
<!-- credits text -->
|
|
4893
5289
|
<span style="
|
|
4894
5290
|
font-size: 52px;
|
|
4895
5291
|
line-height: 1;
|
|
@@ -4901,7 +5297,7 @@ class GenericPackageModal {
|
|
|
4901
5297
|
credits
|
|
4902
5298
|
</span>
|
|
4903
5299
|
|
|
4904
|
-
<!--
|
|
5300
|
+
<!-- Bonus credits -->
|
|
4905
5301
|
${hasBonus ? `
|
|
4906
5302
|
<span style="
|
|
4907
5303
|
font-size: 52px;
|
|
@@ -4917,7 +5313,7 @@ class GenericPackageModal {
|
|
|
4917
5313
|
</div>
|
|
4918
5314
|
</div>
|
|
4919
5315
|
|
|
4920
|
-
<!--
|
|
5316
|
+
<!-- Subtitle - Valid for all platform products -->
|
|
4921
5317
|
<div style="
|
|
4922
5318
|
font-size: 22px;
|
|
4923
5319
|
line-height: 1.4;
|
|
@@ -4928,7 +5324,7 @@ class GenericPackageModal {
|
|
|
4928
5324
|
Valid for all platform products
|
|
4929
5325
|
</div>
|
|
4930
5326
|
|
|
4931
|
-
<!--
|
|
5327
|
+
<!-- Buy button -->
|
|
4932
5328
|
<button
|
|
4933
5329
|
data-package-button="${pkg.id}"
|
|
4934
5330
|
style="
|
|
@@ -4956,201 +5352,6 @@ class GenericPackageModal {
|
|
|
4956
5352
|
</div>
|
|
4957
5353
|
`;
|
|
4958
5354
|
}
|
|
4959
|
-
/**
|
|
4960
|
-
* 格式化数字(添加逗号)
|
|
4961
|
-
*/
|
|
4962
|
-
formatNumber(num) {
|
|
4963
|
-
return parseInt(num).toLocaleString();
|
|
4964
|
-
}
|
|
4965
|
-
/**
|
|
4966
|
-
* 获取加载按钮的 HTML(带旋转动画)
|
|
4967
|
-
*/
|
|
4968
|
-
getLoadingButtonHTML(text) {
|
|
4969
|
-
return `
|
|
4970
|
-
<svg style="
|
|
4971
|
-
display: inline-block;
|
|
4972
|
-
width: 16px;
|
|
4973
|
-
height: 16px;
|
|
4974
|
-
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
4975
|
-
border-top-color: white;
|
|
4976
|
-
border-radius: 50%;
|
|
4977
|
-
animation: spin 0.6s linear infinite;
|
|
4978
|
-
" viewBox="0 0 24 24"></svg>
|
|
4979
|
-
<style>@keyframes spin { to { transform: rotate(360deg); } }</style>
|
|
4980
|
-
<span style="margin-left: 8px;">${text}</span>
|
|
4981
|
-
`;
|
|
4982
|
-
}
|
|
4983
|
-
/**
|
|
4984
|
-
* 添加事件监听
|
|
4985
|
-
*/
|
|
4986
|
-
attachEventListeners(container) {
|
|
4987
|
-
// 为每个套餐按钮添加点击事件
|
|
4988
|
-
this.options.packages.forEach(pkg => {
|
|
4989
|
-
const button = container.querySelector(`[data-package-button="${pkg.id}"]`);
|
|
4990
|
-
if (button) {
|
|
4991
|
-
button.addEventListener('click', async (e) => {
|
|
4992
|
-
e.preventDefault();
|
|
4993
|
-
e.stopPropagation();
|
|
4994
|
-
console.log('[GenericPackageModal] Package selected:', pkg.id);
|
|
4995
|
-
// 保存原始按钮文本
|
|
4996
|
-
const originalText = button.innerHTML;
|
|
4997
|
-
const originalDisabled = button.disabled;
|
|
4998
|
-
try {
|
|
4999
|
-
// 禁用按钮,显示初始化状态
|
|
5000
|
-
button.disabled = true;
|
|
5001
|
-
const isZh = this.language === 'zh-CN';
|
|
5002
|
-
button.innerHTML = this.getLoadingButtonHTML(isZh ? '初始化中...' : 'Initializing...');
|
|
5003
|
-
// 等待 SDK 初始化(支持重试)
|
|
5004
|
-
const initialized = await this.waitForSDKInitialization(30000, 1);
|
|
5005
|
-
if (!initialized) {
|
|
5006
|
-
throw new Error('SDK initialization failed or timed out. Please try again.');
|
|
5007
|
-
}
|
|
5008
|
-
// SDK 初始化成功,执行支付流程
|
|
5009
|
-
await this.handlePaymentFlow(pkg, button, originalText);
|
|
5010
|
-
}
|
|
5011
|
-
catch (error) {
|
|
5012
|
-
console.error('[GenericPackageModal] Failed to process payment:', error);
|
|
5013
|
-
// 恢复按钮状态
|
|
5014
|
-
button.disabled = originalDisabled;
|
|
5015
|
-
button.innerHTML = originalText;
|
|
5016
|
-
// 显示错误提示(使用自定义 UI 替代 alert)
|
|
5017
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
5018
|
-
showErrorMessage(`Payment failed: ${errorMessage}`);
|
|
5019
|
-
// 触发失败回调
|
|
5020
|
-
this.options.onPaymentFailed?.(error instanceof Error ? error : new Error(String(error)), pkg);
|
|
5021
|
-
}
|
|
5022
|
-
});
|
|
5023
|
-
}
|
|
5024
|
-
});
|
|
5025
|
-
}
|
|
5026
|
-
/**
|
|
5027
|
-
* 处理支付流程
|
|
5028
|
-
*/
|
|
5029
|
-
async handlePaymentFlow(pkg, button, originalHTML) {
|
|
5030
|
-
try {
|
|
5031
|
-
// 更新按钮状态为"创建订单中"
|
|
5032
|
-
button.innerHTML = this.getLoadingButtonHTML('Creating order...');
|
|
5033
|
-
console.log('[GenericPackageModal] Creating order for package:', pkg.id);
|
|
5034
|
-
// 调用回调创建订单,或使用默认实现
|
|
5035
|
-
let orderId;
|
|
5036
|
-
// 使用默认实现:调用解析后的 orderApiUrl
|
|
5037
|
-
const resolvedOrderApiUrl = this.options.sdkConfig._resolvedOrderApiUrl || ENVIRONMENT_CONFIGS[this.options.sdkConfig.environment].orderApiUrl;
|
|
5038
|
-
const response = await createOrder(resolvedOrderApiUrl, this.options.sdkConfig.accountToken || '', {
|
|
5039
|
-
product_id: pkg.id,
|
|
5040
|
-
purchase_type: this.options.sdkConfig.businessType ?? 1, // 默认为 1
|
|
5041
|
-
});
|
|
5042
|
-
console.log('[GenericPackageModal] Create order response:', response);
|
|
5043
|
-
if (!response || !response.transaction_id) {
|
|
5044
|
-
throw new Error('Failed to create order: Invalid response from API');
|
|
5045
|
-
}
|
|
5046
|
-
orderId = response.transaction_id;
|
|
5047
|
-
console.log('[GenericPackageModal] Order created:', orderId);
|
|
5048
|
-
if (!orderId) {
|
|
5049
|
-
throw new Error('Order ID not returned');
|
|
5050
|
-
}
|
|
5051
|
-
// 创建并打开支付弹框(按钮状态将在支付完成后处理)
|
|
5052
|
-
await this.openPaymentModal(orderId, pkg);
|
|
5053
|
-
// 支付弹框打开后,恢复按钮状态
|
|
5054
|
-
button.disabled = false;
|
|
5055
|
-
button.innerHTML = originalHTML;
|
|
5056
|
-
}
|
|
5057
|
-
catch (error) {
|
|
5058
|
-
console.error('[GenericPackageModal] Payment flow failed:', error);
|
|
5059
|
-
// 恢复按钮状态
|
|
5060
|
-
button.disabled = false;
|
|
5061
|
-
button.innerHTML = originalHTML;
|
|
5062
|
-
// 显示错误提示(使用自定义 UI 替代 alert)
|
|
5063
|
-
showErrorMessage(`Payment failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
5064
|
-
// 触发失败回调
|
|
5065
|
-
this.options.onPaymentFailed?.(error instanceof Error ? error : new Error(String(error)), pkg);
|
|
5066
|
-
}
|
|
5067
|
-
}
|
|
5068
|
-
/**
|
|
5069
|
-
* 打开支付弹框
|
|
5070
|
-
*/
|
|
5071
|
-
async openPaymentModal(orderId, pkg) {
|
|
5072
|
-
if (!this.paymentMethod) {
|
|
5073
|
-
throw new Error('Payment method not configured');
|
|
5074
|
-
}
|
|
5075
|
-
const pkgName = pkg.name;
|
|
5076
|
-
// 创建支付实例(使用 orderId)
|
|
5077
|
-
if (!this.sdkInitialized) {
|
|
5078
|
-
throw new Error('Payment SDK not initialized. Please provide sdkConfig.');
|
|
5079
|
-
}
|
|
5080
|
-
const paymentInstance = window.SeaartPaymentComponent.createPayment({
|
|
5081
|
-
sys_order_id: orderId,
|
|
5082
|
-
account_token: this.accountToken,
|
|
5083
|
-
});
|
|
5084
|
-
const dropinModal = new DropinPaymentModal(paymentInstance, orderId, this.accountToken, this.paymentMethod, {
|
|
5085
|
-
modalTitle: `Purchase ${pkgName}`,
|
|
5086
|
-
onCompleted: (payload) => {
|
|
5087
|
-
console.log('[GenericPackageModal] Payment completed:', payload);
|
|
5088
|
-
this.close();
|
|
5089
|
-
// 显示购买成功弹窗
|
|
5090
|
-
const successModal = new PurchaseSuccessModal({
|
|
5091
|
-
data: {
|
|
5092
|
-
packName: pkg.name,
|
|
5093
|
-
credits: parseInt(pkg.credits),
|
|
5094
|
-
amount: pkg.price,
|
|
5095
|
-
currency: pkg.currency === 'USD' ? '$' : pkg.currency,
|
|
5096
|
-
orderId: orderId,
|
|
5097
|
-
transactionId: payload.transaction_id,
|
|
5098
|
-
},
|
|
5099
|
-
language: this.language,
|
|
5100
|
-
onClose: () => {
|
|
5101
|
-
// 弹窗关闭后刷新积分
|
|
5102
|
-
(async () => {
|
|
5103
|
-
try {
|
|
5104
|
-
const envConfig = ENVIRONMENT_CONFIGS[this.options.sdkConfig.environment];
|
|
5105
|
-
const walletApiUrl = envConfig.walletApiUrl;
|
|
5106
|
-
console.log('[GenericPackageModal] Refreshing credits from:', walletApiUrl);
|
|
5107
|
-
const creditDetail = await getCreditDetail(walletApiUrl, this.options.sdkConfig.accountToken);
|
|
5108
|
-
if (creditDetail) {
|
|
5109
|
-
console.log('[GenericPackageModal] Credits refreshed, total balance:', creditDetail.total_balance);
|
|
5110
|
-
}
|
|
5111
|
-
else {
|
|
5112
|
-
console.warn('[GenericPackageModal] Failed to refresh credits');
|
|
5113
|
-
}
|
|
5114
|
-
}
|
|
5115
|
-
catch (error) {
|
|
5116
|
-
console.error('[GenericPackageModal] Failed to refresh credits:', error);
|
|
5117
|
-
}
|
|
5118
|
-
})();
|
|
5119
|
-
// 触发用户回调
|
|
5120
|
-
this.options.onPaymentSuccess?.(orderId, payload.transaction_id, pkg);
|
|
5121
|
-
},
|
|
5122
|
-
});
|
|
5123
|
-
successModal.open();
|
|
5124
|
-
},
|
|
5125
|
-
onFailed: (payload) => {
|
|
5126
|
-
console.error('[GenericPackageModal] Payment failed:', payload);
|
|
5127
|
-
const error = new Error(payload.message || 'Payment failed');
|
|
5128
|
-
this.options.onPaymentFailed?.(error, pkg);
|
|
5129
|
-
},
|
|
5130
|
-
onError: (payload, error) => {
|
|
5131
|
-
console.error('[GenericPackageModal] Payment error:', error);
|
|
5132
|
-
this.options.onPaymentFailed?.(error, pkg);
|
|
5133
|
-
},
|
|
5134
|
-
});
|
|
5135
|
-
await dropinModal.open();
|
|
5136
|
-
}
|
|
5137
|
-
/**
|
|
5138
|
-
* 清理资源
|
|
5139
|
-
*/
|
|
5140
|
-
cleanup() {
|
|
5141
|
-
console.log('[GenericPackageModal] Cleaning up...');
|
|
5142
|
-
// 移除resize监听器
|
|
5143
|
-
if (this.resizeHandler) {
|
|
5144
|
-
window.removeEventListener('resize', this.resizeHandler);
|
|
5145
|
-
this.resizeHandler = null;
|
|
5146
|
-
}
|
|
5147
|
-
}
|
|
5148
|
-
/**
|
|
5149
|
-
* 检查弹框是否打开
|
|
5150
|
-
*/
|
|
5151
|
-
isOpen() {
|
|
5152
|
-
return this.modal.isModalOpen();
|
|
5153
|
-
}
|
|
5154
5355
|
}
|
|
5155
5356
|
|
|
5156
5357
|
/**
|
|
@@ -21763,5 +21964,5 @@ var iconYuebuzuBaakZaa_ = /*#__PURE__*/Object.freeze({
|
|
|
21763
21964
|
default: iconYuebuzu
|
|
21764
21965
|
});
|
|
21765
21966
|
|
|
21766
|
-
export { API_ENDPOINTS$1 as API_ENDPOINTS, BIZ_CODE, BindCardPaymentComponent, COMPONENT_LOAD_TIMEOUT, CREATIVE_POWER_TYPES, CREDIT_PACKAGES, CheckoutAPI, CreditPackageModal, DEFAULT_CHECKOUT_CONFIG, DropinPaymentComponent, DropinPaymentModal, ENVIRONMENT_CONFIGS, ENV_CONFIG, ErrorHandler, GRID_COLUMNS, GenericPackageModal, HTTP_STATUS, LinkPaymentComponent, OrderPayment, PAYMENT_ELEMENT_NAME, PaymentAPIError, PaymentCheckoutClient, PaymentClient, PaymentError, PaymentModal, PaymentStorage, PurchaseSuccessModal, RESPONSIVE_BREAKPOINTS, SDK_CONFIG, ScriptLoader, SeaArtPayLoader, SeaartPaymentSDK, StylesheetLoader, VERSION$2 as VERSION, centsToDollars, changeSubscription, checkOrderStatus, createCheckoutPaymentError, createOrder, delay, dollarsToCents, formatPrice, generateOrderReference, getActiveSubscription, getCreditDetail, getCurrentSubscription, getCurrentUrl, getGlobalLoader, getSDKLocale, hideLoadingIndicator, isBrowser, isCheckoutPaymentError, pollOrderStatus, resetGlobalLoader, restartSubscription, safeJsonParse, showErrorMessage, showInfoMessage, showLoadingIndicator, showSuccessMessage, withTimeout };
|
|
21967
|
+
export { API_ENDPOINTS$1 as API_ENDPOINTS, BIZ_CODE, BindCardPaymentComponent, COMPONENT_LOAD_TIMEOUT, CREATIVE_POWER_TYPES, CREDIT_PACKAGES, CheckoutAPI, CreditPackageModal, DEFAULT_CHECKOUT_CONFIG, DropinPaymentComponent, DropinPaymentModal, ENVIRONMENT_CONFIGS, ENV_CONFIG, ErrorHandler, GRID_COLUMNS, GenericPackageModal, HTTP_STATUS, LinkPaymentComponent, OrderPayment, PAYMENT_ELEMENT_NAME, PaymentAPIError, PaymentCheckoutClient, PaymentClient, PaymentError, PaymentModal, PaymentStorage, PurchaseSuccessModal, RESPONSIVE_BREAKPOINTS, RetentionModal, SDK_CONFIG, ScriptLoader, SeaArtPayLoader, SeaartPaymentSDK, StylesheetLoader, VERSION$2 as VERSION, centsToDollars, changeSubscription, checkOrderStatus, createCheckoutPaymentError, createOrder, delay, dollarsToCents, formatPrice, generateOrderReference, getActiveSubscription, getCreditDetail, getCurrentSubscription, getCurrentUrl, getGlobalLoader, getSDKLocale, hideLoadingIndicator, isBrowser, isCheckoutPaymentError, pollOrderStatus, resetGlobalLoader, restartSubscription, safeJsonParse, showErrorMessage, showInfoMessage, showLoadingIndicator, showSuccessMessage, withTimeout };
|
|
21767
21968
|
//# sourceMappingURL=index.browser.js.map
|