Files
gpt-plus-gpt/pkg/stripe/telemetry.go
2026-03-15 20:48:19 +08:00

307 lines
11 KiB
Go

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)
}