// background/checkout-api-utils.js — API helpers for Stripe/PayPal checkout automation (function attachCheckoutApiUtils(root) { root.MultiPageCheckoutApiUtils = (function createCheckoutApiUtilsModule() { const ADDRESS_API_URL = 'https://www.meiguodizhi.com/api/v1/dz'; const CHATGPT_CHECKOUT_URL = 'https://chatgpt.com/backend-api/payments/checkout'; const DEFAULT_PAYPAL_SMS_MAX_ATTEMPTS = 18; const DEFAULT_PAYPAL_SMS_INTERVAL_MS = 5000; /** * Fetch a random US address from meiguodizhi.com. * @returns {Promise<{street: string, city: string, state: string, zip: string}>} */ async function fetchRandomAddress() { try { const response = await fetch(ADDRESS_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: '/', method: 'address' }), }); const d = await response.json(); const a = d.address || d; return { street: a.Address || a.street || '123 Main St', city: a.City || a.city || 'New York', state: a.State_Full || a.State || a.state || 'New York', zip: String(a.Zip_Code || a.zip || '10001').substring(0, 5), }; } catch (e) { console.error('[CheckoutAPI] 获取随机地址失败:', e.message); return { street: '123 Main St', city: 'New York', state: 'New York', zip: '10001' }; } } /** * Generate a ChatGPT Plus Stripe hosted checkout URL. * @param {string} accessToken - ChatGPT session access token * @returns {Promise<{hostedUrl: string, checkoutSessionId: string}|null>} */ async function generateCheckoutLink(accessToken) { try { const payload = { plan_name: 'chatgptplusplan', billing_details: { country: 'US', currency: 'USD' }, cancel_url: 'https://chatgpt.com/#pricing', promo_campaign: { promo_campaign_id: 'plus-1-month-free', is_coupon_from_query_param: false }, checkout_ui_mode: 'hosted', }; const response = await fetch(CHATGPT_CHECKOUT_URL, { method: 'POST', headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); const data = await response.json(); if (!response.ok) { console.error('[CheckoutAPI] 请求失败 HTTP', response.status, data); return null; } const hostedUrl = data?.url || data?.stripe_hosted_url || data?.checkout_url; if (!hostedUrl) { console.error('[CheckoutAPI] 未找到长链接', data); return null; } return { hostedUrl, checkoutSessionId: data.checkout_session_id || '', }; } catch (e) { console.error('[CheckoutAPI] 生成 Plus 链接异常:', e.message); return null; } } function collectSmsTextValues(value, results = []) { if (value === null || value === undefined) { return results; } if (typeof value === 'string' || typeof value === 'number') { results.push(String(value)); return results; } if (Array.isArray(value)) { value.forEach((item) => collectSmsTextValues(item, results)); return results; } if (typeof value === 'object') { const preferredKeys = [ 'code', 'sms', 'message', 'msg', 'content', 'text', 'data', 'result', ]; preferredKeys.forEach((key) => { if (Object.prototype.hasOwnProperty.call(value, key)) { collectSmsTextValues(value[key], results); } }); Object.keys(value) .filter((key) => !preferredKeys.includes(key)) .forEach((key) => collectSmsTextValues(value[key], results)); } return results; } function extractPaypalSmsCode(rawPayload, options = {}) { const excluded = new Set((options.excludeCodes || []).map((code) => String(code || '').trim()).filter(Boolean)); const rawText = String(rawPayload || '').trim(); if (!rawText) return ''; let values = [rawText]; try { values = collectSmsTextValues(JSON.parse(rawText)); } catch { // Plain text API responses are expected. } const candidates = []; values.forEach((value) => { String(value || '').replace(/\b(\d{4,8})\b/g, (_, code) => { if (!excluded.has(code)) { candidates.push(code); } return _; }); }); return candidates.find((code) => code.length === 6) || candidates.find((code) => code.length >= 4 && code.length <= 8) || ''; } async function fetchPaypalSmsCode(options = {}, deps = {}) { const fetchImpl = deps.fetchImpl || fetch; const sleep = deps.sleep || ((ms) => new Promise((resolve) => setTimeout(resolve, ms))); const addLog = deps.addLog || (async () => {}); const throwIfStopped = deps.throwIfStopped || (() => {}); const smsApiUrl = String(options.smsApiUrl || '').trim(); if (!smsApiUrl) { throw new Error('PayPal 短信收码 API 未配置。'); } const maxAttempts = Math.max(1, Math.floor(Number(options.maxAttempts) || DEFAULT_PAYPAL_SMS_MAX_ATTEMPTS)); const intervalMs = Math.max(500, Math.floor(Number(options.intervalMs) || DEFAULT_PAYPAL_SMS_INTERVAL_MS)); let lastError = ''; for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { throwIfStopped(); try { const response = await fetchImpl(smsApiUrl, { method: 'GET', cache: 'no-store', }); const text = await response.text(); if (!response.ok) { lastError = `HTTP ${response.status}: ${text.slice(0, 120)}`; } else { const code = extractPaypalSmsCode(text, { excludeCodes: options.excludeCodes || [], }); if (code) { return { code, attempt, raw: text, }; } lastError = text.slice(0, 160) || '接口未返回验证码'; } } catch (error) { lastError = error?.message || String(error || '请求失败'); } if (attempt < maxAttempts) { await addLog(`PayPal 短信验证码暂未获取到,${Math.round(intervalMs / 1000)} 秒后重试(${attempt}/${maxAttempts})。`, 'info'); await sleep(intervalMs); } } throw new Error(`PayPal 短信验证码获取超时。最后响应:${lastError || '无响应'}`); } return { extractPaypalSmsCode, fetchRandomAddress, fetchPaypalSmsCode, generateCheckoutLink, }; })(); })(typeof self !== 'undefined' ? self : globalThis);