Files
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

94 lines
3.0 KiB
Go

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
}