292 lines
11 KiB
JavaScript
292 lines
11 KiB
JavaScript
'use strict';
|
||
/**
|
||
* Stripe hCaptcha 全流程 Runner
|
||
*
|
||
* 流程:
|
||
* 1. 解析 Stripe Checkout URL → pk_live + session_id
|
||
* 2. 调用 Stripe init API → sitekey + rqdata
|
||
* 3. hCaptcha 求解 → token
|
||
*
|
||
* 使用:
|
||
* node run_solver.js [stripe_checkout_url]
|
||
* node run_solver.js # 使用默认 URL 测试
|
||
*/
|
||
|
||
const { HCaptchaSolver } = require('./src/hcaptcha_solver');
|
||
const { Logger } = require('./src/utils/logger');
|
||
|
||
const logger = new Logger('run_solver');
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// 步骤 1: 从 Stripe Checkout URL 提取参数
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
/**
|
||
* 解析 Stripe Checkout URL
|
||
* 格式: https://pay.xxx.com/c/pay/cs_live_XXXX#pk_live_XXXX...
|
||
*
|
||
* @param {string} url - Stripe Checkout URL
|
||
* @returns {{ sessionId: string, pkLive: string }}
|
||
*/
|
||
function parseStripeUrl(url) {
|
||
logger.info('=== 开始获取 Stripe Checkout 参数 ===');
|
||
logger.info(`URL: ${url.substring(0, 80)}...`);
|
||
|
||
// [步骤 1] 从 hash 解码 pk_live
|
||
logger.info('[步骤 1] 从 URL hash 解码 pk_live...');
|
||
const hashPart = url.split('#')[1] || '';
|
||
let pkLive = '';
|
||
if (hashPart) {
|
||
// hash 可能直接是 pk_live_XXXX 或 base64 编码的 JSON
|
||
if (hashPart.startsWith('pk_live_') || hashPart.startsWith('pk_test_')) {
|
||
pkLive = hashPart;
|
||
} else {
|
||
try {
|
||
const decoded = Buffer.from(hashPart, 'base64').toString();
|
||
const data = JSON.parse(decoded);
|
||
pkLive = data.pk || data.key || '';
|
||
} catch {
|
||
// 可能是 URL-encoded
|
||
try {
|
||
pkLive = decodeURIComponent(hashPart);
|
||
} catch {
|
||
pkLive = hashPart;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
logger.info(` 提取到 pk_live: ${pkLive.substring(0, 50)}...`);
|
||
|
||
// [步骤 2] 从路径提取 session_id
|
||
const urlObj = new URL(url.split('#')[0]);
|
||
const pathParts = urlObj.pathname.split('/');
|
||
const sessionId = pathParts.find(p => p.startsWith('cs_live_') || p.startsWith('cs_test_')) || '';
|
||
logger.info(`[步骤 2] 提取到 Session ID: ${sessionId}`);
|
||
|
||
return { sessionId, pkLive };
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// 步骤 2: 调用 Stripe Init API 获取 hCaptcha 参数
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
async function fetchStripeParams(sessionId, pkLive) {
|
||
const initUrl = `https://api.stripe.com/v1/payment_pages/${sessionId}/init`;
|
||
logger.info(`[步骤 3] 正在调用 Init API: ${initUrl.substring(0, 80)}...`);
|
||
|
||
const resp = await fetch(initUrl, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'Authorization': `Bearer ${pkLive}`,
|
||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
|
||
'Origin': 'https://checkout.stripe.com',
|
||
'Referer': 'https://checkout.stripe.com/',
|
||
},
|
||
body: new URLSearchParams({
|
||
'key': pkLive,
|
||
}),
|
||
});
|
||
|
||
logger.info(`HTTP POST ${initUrl.substring(0, 80)}... → ${resp.status}`);
|
||
|
||
if (!resp.ok) {
|
||
const text = await resp.text();
|
||
throw new Error(`Stripe Init API 失败 (${resp.status}): ${text.substring(0, 200)}`);
|
||
}
|
||
|
||
const data = await resp.json();
|
||
|
||
// 从返回数据中提取 hCaptcha 相关字段
|
||
// Stripe 返回的 JSON 结构中通常包含 hcaptcha_site_key 和 hcaptcha_rqdata
|
||
let siteKey = '';
|
||
let rqdata = '';
|
||
let host = 'b.stripecdn.com';
|
||
|
||
// 递归搜索 JSON 中的 hcaptcha 字段
|
||
const findHcaptchaParams = (obj, path = '') => {
|
||
if (!obj || typeof obj !== 'object') return;
|
||
for (const [k, v] of Object.entries(obj)) {
|
||
const key = k.toLowerCase();
|
||
if (key.includes('hcaptcha_site_key') || key === 'site_key' || key === 'sitekey') {
|
||
if (typeof v === 'string' && v.length > 10) siteKey = v;
|
||
}
|
||
if (key.includes('hcaptcha_rqdata') || key === 'rqdata') {
|
||
if (typeof v === 'string' && v.length > 10) rqdata = v;
|
||
}
|
||
if (key === 'host' && typeof v === 'string' && v.includes('.')) {
|
||
host = v;
|
||
}
|
||
if (typeof v === 'object' && v !== null) {
|
||
findHcaptchaParams(v, `${path}.${k}`);
|
||
}
|
||
}
|
||
};
|
||
findHcaptchaParams(data);
|
||
|
||
// Stripe 常见的返回路径
|
||
if (!siteKey && data.hcaptcha_site_key) siteKey = data.hcaptcha_site_key;
|
||
if (!siteKey && data.captcha?.hcaptcha?.site_key) siteKey = data.captcha.hcaptcha.site_key;
|
||
if (!rqdata && data.hcaptcha_rqdata) rqdata = data.hcaptcha_rqdata;
|
||
if (!rqdata && data.captcha?.hcaptcha?.rqdata) rqdata = data.captcha.hcaptcha.rqdata;
|
||
|
||
logger.info(` 成功获取 rqdata: ${rqdata ? rqdata.substring(0, 50) + '...' : '(空)'}`);
|
||
logger.info(` 成功获取 site_key: ${siteKey}`);
|
||
logger.info('=== Stripe 参数获取完成 ===');
|
||
|
||
return { siteKey, rqdata, host };
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// 步骤 3: hCaptcha 求解
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
async function solveHCaptcha(siteKey, host, rqdata) {
|
||
console.log('');
|
||
console.log('=== 开始求解 hCaptcha ===');
|
||
console.log(`Host: ${host}`);
|
||
console.log(`Sitekey: ${siteKey}`);
|
||
console.log('');
|
||
|
||
const solver = new HCaptchaSolver({
|
||
sitekey: siteKey,
|
||
host,
|
||
rqdata,
|
||
});
|
||
|
||
return solver.solve();
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// 直接用 sitekey 测试(跳过 Stripe 部分)
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
async function directSolve(sitekey, host, rqdata) {
|
||
console.log('');
|
||
console.log('=== 直接模式 (跳过 Stripe) ===');
|
||
console.log(`Sitekey: ${sitekey}`);
|
||
console.log(`Host: ${host}`);
|
||
if (rqdata) console.log(`Rqdata: ${rqdata.substring(0, 50)}...`);
|
||
console.log('');
|
||
|
||
return solveHCaptcha(sitekey, host, rqdata);
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// 主入口
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
async function main() {
|
||
const args = process.argv.slice(2);
|
||
|
||
// 配置项(可按需修改)
|
||
const CONFIG = {
|
||
// Stripe Checkout URL(测试用)
|
||
stripeUrl: 'https://pay.verdent.ai/c/pay/cs_live_a1H5uyD1bkpXKyqaw0BXzwzGrdzTngoNXBO6ejdyvCmswD9D6Cqzy7URwB#pk_live_51S5juuHIX9Hc8tITIZnW34rV6PJhIzl66WgEZ8kLv',
|
||
// 直接模式参数(跳过 Stripe API 调用,直接测试 hCaptcha)
|
||
direct: {
|
||
sitekey: 'ec637546-e9b8-447a-ab81-b5fb6d228ab8',
|
||
host: 'b.stripecdn.com',
|
||
rqdata: '', // 留空则不附加 rqdata
|
||
},
|
||
};
|
||
|
||
let mode = 'direct'; // 默认直接模式
|
||
let stripeUrl = CONFIG.stripeUrl;
|
||
|
||
// 解析命令行参数
|
||
for (let i = 0; i < args.length; i++) {
|
||
if (args[i] === '--stripe' || args[i] === '-s') {
|
||
mode = 'stripe';
|
||
if (args[i + 1] && !args[i + 1].startsWith('-')) {
|
||
stripeUrl = args[++i];
|
||
}
|
||
} else if (args[i] === '--direct' || args[i] === '-d') {
|
||
mode = 'direct';
|
||
} else if (args[i] === '--sitekey') {
|
||
CONFIG.direct.sitekey = args[++i];
|
||
} else if (args[i] === '--host') {
|
||
CONFIG.direct.host = args[++i];
|
||
} else if (args[i] === '--rqdata') {
|
||
CONFIG.direct.rqdata = args[++i];
|
||
} else if (args[i] === '--help' || args[i] === '-h') {
|
||
console.log(`
|
||
Stripe hCaptcha Solver Runner
|
||
|
||
用法:
|
||
node run_solver.js 直接模式 (默认 sitekey)
|
||
node run_solver.js --direct --sitekey KEY --host H 指定参数直接模式
|
||
node run_solver.js --stripe [URL] Stripe 全流程模式
|
||
node run_solver.js --stripe https://pay.xxx.com/... 指定 Stripe URL
|
||
|
||
选项:
|
||
--direct, -d 直接模式 (跳过 Stripe, 默认)
|
||
--stripe, -s Stripe 全流程模式
|
||
--sitekey KEY hCaptcha sitekey
|
||
--host HOST hCaptcha host
|
||
--rqdata DATA 附加 rqdata
|
||
--help, -h 显示帮助
|
||
`);
|
||
process.exit(0);
|
||
} else if (args[i].startsWith('http')) {
|
||
mode = 'stripe';
|
||
stripeUrl = args[i];
|
||
}
|
||
}
|
||
|
||
console.log(`\n${'═'.repeat(60)}`);
|
||
console.log(' Stripe hCaptcha Solver Runner');
|
||
console.log(`${'═'.repeat(60)}\n`);
|
||
|
||
let result;
|
||
|
||
if (mode === 'stripe') {
|
||
// ── 完整 Stripe 流程 ──
|
||
const { sessionId, pkLive } = parseStripeUrl(stripeUrl);
|
||
|
||
if (!sessionId || !pkLive) {
|
||
logger.error('无法从 URL 提取 sessionId 或 pk_live');
|
||
process.exit(1);
|
||
}
|
||
|
||
const { siteKey, rqdata, host } = await fetchStripeParams(sessionId, pkLive);
|
||
|
||
if (!siteKey) {
|
||
logger.error('无法从 Stripe 获取 hCaptcha sitekey');
|
||
process.exit(1);
|
||
}
|
||
|
||
result = await solveHCaptcha(siteKey, host, rqdata);
|
||
} else {
|
||
// ── 直接测试模式 ──
|
||
result = await directSolve(
|
||
CONFIG.direct.sitekey,
|
||
CONFIG.direct.host,
|
||
CONFIG.direct.rqdata,
|
||
);
|
||
}
|
||
|
||
// ── 输出结果 ──
|
||
console.log('');
|
||
console.log('═'.repeat(60));
|
||
if (result.success) {
|
||
console.log(' ✅ 求解成功!');
|
||
if (typeof result.token === 'string') {
|
||
console.log(` Token: ${result.token.substring(0, 60)}...`);
|
||
}
|
||
} else {
|
||
console.log(' ❌ 求解失败');
|
||
if (result.error) console.log(` Error: ${result.error}`);
|
||
}
|
||
console.log('═'.repeat(60));
|
||
console.log('');
|
||
|
||
process.exit(result.success ? 0 : 1);
|
||
}
|
||
|
||
main().catch(err => {
|
||
logger.error(`未捕获异常: ${err.message}`);
|
||
console.error(err.stack);
|
||
process.exit(1);
|
||
});
|