package stripe import ( "context" "encoding/json" "fmt" "log" "math/rand" "net/url" "strings" "time" "github.com/google/uuid" "gpt-plus/pkg/captcha" "gpt-plus/pkg/httpclient" ) // TelemetrySession holds state for simulating Stripe.js telemetry events. type TelemetrySession struct { Client *httpclient.Client CheckoutSessionID string PublishableKey string StripeJsID string UserAgent string Timezone string // e.g. "America/Chicago" Currency string // e.g. "usd" Merchant string // e.g. "acct_1HOrSwC6h1nxGoI3" startTime int64 // controller load time (epoch ms) eventCount int stripeObjID string sessionID string // elements_session_id } // NewTelemetrySession creates a telemetry session for simulating stripe.js events. func NewTelemetrySession(client *httpclient.Client, checkoutSessionID, publishableKey, stripeJsID, userAgent, timezone, currency string) *TelemetrySession { return &TelemetrySession{ Client: client, CheckoutSessionID: checkoutSessionID, PublishableKey: publishableKey, StripeJsID: stripeJsID, UserAgent: userAgent, Timezone: timezone, Currency: currency, Merchant: "acct_1HOrSwC6h1nxGoI3", startTime: time.Now().UnixMilli() - int64(rand.Intn(2000)+500), stripeObjID: "sobj-" + uuid.New().String(), sessionID: "elements_session_" + randomAlphaNum(11), } } // PassiveCaptchaSiteKey is the primary hCaptcha site key used for Stripe's passive captcha. // Updated from packet capture (2025-06): confirmed key for confirm requests. const PassiveCaptchaSiteKey = "24ed0064-62cf-4d42-9960-5dd1a41d4e29" // TelemetryStatusFunc is a callback for printing progress to terminal. type TelemetryStatusFunc func(format string, args ...interface{}) // SendPreConfirmEvents sends telemetry events and solves passive captcha. // Returns the passive captcha token to include in confirm request. // Sends 50+ events across 12 batches to match real browser telemetry volume. func (ts *TelemetrySession) SendPreConfirmEvents(ctx context.Context, solver *captcha.Solver, sf TelemetryStatusFunc) (passiveToken string, err error) { log.Printf("[telemetry] sending pre-confirm events for checkout %s", ts.CheckoutSessionID) now := time.Now().UnixMilli() // Batch 1: Init events (5) ts.sendBatch([]event{ ts.makeEvent("elements.controller.load", now-12000), ts.makeEvent("elements.event.load", now-11800), ts.makeEvent("rum.stripejs", now-11600), ts.makeEvent("elements.create", now-11400), ts.makeEvent("elements.init_payment_page", now-11200), }) time.Sleep(time.Duration(200+rand.Intn(300)) * time.Millisecond) // Batch 2: Mount + ready events (5) ts.sendBatch([]event{ ts.makeEvent("elements.mount", now-10500), ts.makeEvent("elements.event.ready", now-10300), ts.makeEvent("elements.init_payment_page.success", now-10100), ts.makeEvent("elements.retrieve_elements_session.success", now-9900), ts.makeEvent("elements.get_elements_state", now-9700), }) time.Sleep(time.Duration(200+rand.Intn(300)) * time.Millisecond) // Batch 3: Card number field interaction (4) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-9000), ts.makeEvent("elements.event.change", now-8200), ts.makeEvent("elements.event.blur", now-7800), ts.makeEvent("elements.update", now-7700), }) time.Sleep(time.Duration(150+rand.Intn(250)) * time.Millisecond) // Batch 4: Card expiry field interaction (4) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-7200), ts.makeEvent("elements.event.change", now-6800), ts.makeEvent("elements.event.blur", now-6400), ts.makeEvent("elements.update", now-6300), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 5: Card CVC field interaction (4) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-5800), ts.makeEvent("elements.event.change", now-5400), ts.makeEvent("elements.event.blur", now-5000), ts.makeEvent("elements.update", now-4900), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 6: Name field interaction (4) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-4500), ts.makeEvent("elements.event.change", now-4100), ts.makeEvent("elements.event.blur", now-3800), ts.makeEvent("elements.update", now-3700), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 7: Address field interaction (4) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-3300), ts.makeEvent("elements.event.change", now-2900), ts.makeEvent("elements.event.blur", now-2600), ts.makeEvent("elements.update", now-2500), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 8: Postal code field interaction + address validation (5) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-2200), ts.makeEvent("elements.event.change", now-1900), ts.makeEvent("elements.event.blur", now-1600), ts.makeEvent("elements.update", now-1500), ts.makeEvent("elements.update_checkout_session", now-1400), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 9: Session state refresh (4) ts.sendBatch([]event{ ts.makeEvent("elements.retrieve_elements_session.success", now-1200), ts.makeEvent("elements.get_elements_state", now-1100), ts.makeEvent("elements.update", now-1000), ts.makeEvent("elements.validate_elements", now-900), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 10: Additional focus/blur cycles (6) ts.sendBatch([]event{ ts.makeEvent("elements.event.focus", now-800), ts.makeEvent("elements.event.blur", now-700), ts.makeEvent("elements.event.focus", now-650), ts.makeEvent("elements.event.blur", now-550), ts.makeEvent("elements.event.focus", now-500), ts.makeEvent("elements.event.blur", now-400), }) time.Sleep(time.Duration(100+rand.Intn(200)) * time.Millisecond) // Batch 11: Passive captcha init (2) ts.sendBatch([]event{ ts.makeEvent("elements.captcha.passive.init", now-300), ts.makeEvent("elements.captcha.passive.load", now-250), }) // Solve invisible hCaptcha via captcha solver if solver != nil { if sf != nil { sf(" → 解 Passive hCaptcha (invisible, site_key=%s)...", PassiveCaptchaSiteKey[:8]) } log.Printf("[telemetry] solving passive (invisible) hCaptcha, site_key=%s", PassiveCaptchaSiteKey) passiveToken, _, err = solver.SolveInvisibleHCaptcha(ctx, PassiveCaptchaSiteKey, "https://js.stripe.com") if err != nil { if sf != nil { sf(" ⚠ Passive hCaptcha 失败 (非致命): %v", err) } log.Printf("[telemetry] passive captcha failed (non-fatal): %v", err) } else { if sf != nil { sf(" ✓ Passive hCaptcha 成功, token 长度=%d", len(passiveToken)) } log.Printf("[telemetry] passive captcha token obtained, len=%d", len(passiveToken)) } } // Batch 12: Passive captcha completion + pre-confirm (5) ts.sendBatch([]event{ ts.makeEvent("elements.captcha.passive.execute", now-100), ts.makeEvent("elements.captcha.passive.success", now-80), ts.makeEvent("elements.get_elements_state", now-60), ts.makeEvent("elements.custom_checkout.confirm", now-30), ts.makeEvent("elements.validate_elements", now-10), }) log.Printf("[telemetry] sent %d events total across 12 batches", ts.eventCount) return passiveToken, nil } // SendPostConfirmEvents sends events after a successful confirm. func (ts *TelemetrySession) SendPostConfirmEvents() { now := time.Now().UnixMilli() ts.sendBatch([]event{ ts.makeEvent("elements.confirm_payment_page", now), ts.makeEvent("elements.confirm_payment_page.success", now+100), }) } // ─── Internal helpers ─── type event map[string]interface{} func (ts *TelemetrySession) makeEvent(name string, created int64) event { ts.eventCount++ e := event{ "event_name": name, "created": created, "batching_enabled": true, "event_count": fmt.Sprintf("%d", ts.eventCount), "os": "Windows", "browserFamily": "Chrome", "version": FetchStripeConstants().BuildHash, "event_id": uuid.New().String(), "team_identifier": "t_0", "deploy_status": "main", "browserClassification": "modern", "browser_classification_v2": "2024", "connection_rtt": fmt.Sprintf("%d", 50+rand.Intn(200)), "connection_downlink": fmt.Sprintf("%.1f", 1.0+rand.Float64()*9), "connection_effective_type": "4g", "key": ts.PublishableKey, "key_mode": "live", "referrer": "https://chatgpt.com", "betas": "custom_checkout_server_updates_1 custom_checkout_manual_approval_1", "stripe_js_id": ts.StripeJsID, "stripe_obj_id": ts.stripeObjID, "controller_load_time": fmt.Sprintf("%d", ts.startTime), "stripe_js_release_train": "basil", "wrapper": "react-stripe-js", "wrapper_version": "3.10.0", "es_module": "true", "es_module_version": "7.9.0", "browser_timezone": ts.Timezone, "checkout_session_id": ts.CheckoutSessionID, "elements_init_source": "custom_checkout", "decoupled_intent": "true", "merchant": ts.Merchant, "elements_session_id": ts.sessionID, } switch { case strings.Contains(name, "focus"), strings.Contains(name, "blur"), strings.Contains(name, "mount"), strings.Contains(name, "ready"): e["element"] = "payment" e["element_id"] = "payment-" + uuid.New().String() e["frame_width"] = fmt.Sprintf("%d", 400+rand.Intn(200)) case strings.Contains(name, "confirm"): e["currency"] = ts.Currency e["frame_width"] = fmt.Sprintf("%d", 800+rand.Intn(200)) e["livemode"] = "true" e["uiMode"] = "custom" e["m_sdk_confirm"] = "1" } return e } func (ts *TelemetrySession) sendBatch(events []event) error { eventsJSON, err := json.Marshal(events) if err != nil { return fmt.Errorf("marshal telemetry events: %w", err) } form := url.Values{} form.Set("client_id", "stripe-js") form.Set("num_requests", fmt.Sprintf("%d", len(events))) form.Set("events", string(eventsJSON)) headers := map[string]string{ "Content-Type": "application/x-www-form-urlencoded", "Origin": "https://js.stripe.com", "Referer": "https://js.stripe.com/", "Accept": "application/json", "Accept-Language": DefaultAcceptLanguage, "User-Agent": ts.UserAgent, } resp, err := ts.Client.PostForm("https://r.stripe.com/b", form, headers) if err != nil { log.Printf("[telemetry] send batch failed: %v", err) return err } httpclient.ReadBody(resp) return nil } func randomAlphaNum(n int) string { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" b := make([]byte, n) for i := range b { b[i] = chars[rand.Intn(len(chars))] } return string(b) }