package rechargeser import ( "fmt" "strings" "unicode/utf8" "91porn-server/models/commod" "91porn-server/models/v/vipcardexperimentmod" "go.mongodb.org/mongo-driver/bson/primitive" ) const maxAttributionValueLength = 128 func (r *RechargeRequest) normalizeAttribution() { r.SourcePage = NormalizeOrderSourcePage(r.SourcePage) r.SourceRef = strings.TrimSpace(r.SourceRef) r.VideoID = strings.TrimSpace(r.VideoID) r.ActivityID = strings.TrimSpace(r.ActivityID) r.ExperimentID = strings.TrimSpace(r.ExperimentID) r.ExperimentVariant = strings.ToUpper(strings.TrimSpace(r.ExperimentVariant)) r.SessionID = strings.TrimSpace(r.SessionID) r.CheckoutContextID = strings.TrimSpace(r.CheckoutContextID) } func (r *RechargeRequest) validateAttribution(uid uint64) error { r.normalizeAttribution() if utf8.RuneCountInString(r.SourcePage) > maxAttributionValueLength { return fmt.Errorf("sourcePage must not exceed %d characters", maxAttributionValueLength) } if r.SourcePage == OrderSourcePageDramaPaywall { if r.BuyType != commod.BuyGold && r.BuyType != commod.BuyProduct { return fmt.Errorf("buyType must be 1 or 4 for DRAMA_PAYWALL") } // sourceRef is the generic order attribution field. For drama paywall // orders, derive it from the backend-issued checkout context instead of // requiring clients to submit the same identifier twice. The remaining // drama fields are attribution-only and must not block a real payment. if len(r.CheckoutContextID) > maxAttributionValueLength { r.CheckoutContextID = "" } r.SourceRef = r.CheckoutContextID } values := map[string]string{ "sourceRef": r.SourceRef, "videoId": r.VideoID, "activityId": r.ActivityID, "experimentId": r.ExperimentID, "experimentVariant": r.ExperimentVariant, "sessionId": r.SessionID, "checkoutContextId": r.CheckoutContextID, } for name, value := range values { if len(value) > maxAttributionValueLength { return fmt.Errorf("%s must not exceed %d characters", name, maxAttributionValueLength) } } if r.ExperimentID == "" { r.ExperimentVariant = "" return nil } if r.ExperimentVariant == "" || r.SessionID == "" { return fmt.Errorf("experimentVariant and sessionId are required with experimentId") } experiment, err := vipcardexperimentmod.FindByExperimentID(r.ExperimentID) if err != nil { return err } if experiment == nil { return fmt.Errorf("experiment does not exist") } config, ok := experiment.ConfigFor(r.ExperimentVariant) if !ok { return fmt.Errorf("experimentVariant must be A or B") } if assigned := experiment.Assign(uid); assigned != r.ExperimentVariant { return fmt.Errorf("experimentVariant does not match user assignment") } if !containsObjectID(config.ProductIDs, r.ProductID) { return fmt.Errorf("productID does not belong to experiment variant") } return nil } func containsObjectID(ids []primitive.ObjectID, target primitive.ObjectID) bool { for _, id := range ids { if id == target { return true } } return false }