package web import ( "net/http" "net/url" "strconv" "strings" "videoweb/app/service/authser" "videoweb/app/service/rechargeser" "videoweb/pub/common/log" "videoweb/pub/common/useragent" "github.com/gin-gonic/gin" ) const rechargePageSize = 5 // Recharge 金币充值页:套餐 GET /api/app/recharge/package/list,购买 POST /api/app/coin/buy,流水 GET /api/app/coin/trans/list。 func Recharge(c *gin.Context) { data := gin.H{ "title": "金币充值 - 黄果短剧", "pageHeading": "金币充值", "backHref": "/profile", "helpHref": "/help", } responses := gin.H{} loadAccountSummary(c, data, responses) packages, err := rechargeser.FetchPackageList(c) if err != nil { log.Error("fetch recharge packages failed", log.E(err)) data["packageUnavailable"] = true packages = &rechargeser.RechargePackageListResp{} responses["recharge/package/list"] = gin.H{"error": "加载失败", "msg": err.Error()} } else { responses["recharge/package/list"] = gin.H{"code": 200, "data": packages} } vm := rechargeser.BuildDepositPageVM(packages, "coin") data["packages"] = vm.CoinList data["paymentMethods"] = vm.PayMethods data["rechargePackageResponse"] = packages balance := authser.RechargeFromCookies(c) if _, ok := data["recharge"]; ok { balance = toInt64(data["recharge"]) } data["balance"] = balance data["balanceText"] = authser.FormatRecharge(balance) page, _, err := fetchRechargeTransactions(c, 1, rechargePageSize, 0) if err != nil { log.Error("fetch recharge transactions failed", log.E(err)) data["transactionsUnavailable"] = true page = &rechargeser.CoinTransPage{PageNum: 1, PageSize: rechargePageSize} responses["coin/trans/list"] = gin.H{"error": "加载失败", "msg": err.Error()} } else { responses["coin/trans/list"] = gin.H{"code": 200, "data": page} } data["transactions"] = rechargeser.BuildTxListFromCoinTrans(page.List, 0) data["transactionPage"] = page data["transactionResponse"] = page data["rechargeDebug"] = responses HTMLOK(c, "recharge.html", data) } // RechargeCoinBuy BFF:代理 POST /api/app/coin/buy。 // 文档:http://172.104.181.99:25319/swagger/index.html#/金币/post_api_app_coin_buy func RechargeCoinBuy(c *gin.Context) { if useragent.TokenFromRequest(c.Request) == "" { c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) return } var body rechargeser.BuyPayReq if err := c.ShouldBindJSON(&body); err != nil || body.PackageID <= 0 { c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "packageId required", "tip": "请选择充值套餐", "data": nil}) return } body.PayMode = rechargeser.NormalizePayMode(body.PayMode) if body.PayMode == "" { c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "payMode required", "tip": "请选择支付方式", "data": nil}) return } if !validCoinPackagePayment(c, body.PackageID, body.PayMode) { c.JSON(http.StatusOK, gin.H{"code": 400, "msg": "invalid package/payMode", "tip": "套餐或支付方式不可用,请刷新后重试", "data": nil}) return } env, err := rechargeser.BuyCoin(c, body) if err != nil { log.Error("buy coin failed", log.E(err)) c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "创建支付订单失败,请稍后重试", "data": nil}) return } if env != nil { if retry, needLogin := authser.OnSessionExpired(c, env.Code); needLogin { c.JSON(http.StatusOK, gin.H{"code": env.Code, "msg": env.Msg, "tip": "登录已过期,请重新登录", "data": nil}) return } else if retry { env, err = rechargeser.BuyCoin(c, body) if err != nil { log.Error("buy coin retry failed", log.E(err)) c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "创建支付订单失败,请稍后重试", "data": nil}) return } } } if env == nil || env.Code != 200 { code, tip, msg := 500, "创建支付订单失败", "buy failed" if env != nil { code = env.Code msg = env.Msg tip = firstNonEmptyAuth(env.Tip, env.Msg, tip) } c.JSON(http.StatusOK, gin.H{"code": code, "msg": msg, "tip": tip, "data": nil}) return } safeURL := safePaymentURL(env.Data.PayURL) if safeURL == "" { c.JSON(http.StatusOK, gin.H{"code": 500, "msg": "invalid payUrl", "tip": "支付地址无效,请稍后重试", "data": nil}) return } env.Data.PayURL = safeURL c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": env.Msg, "tip": firstNonEmptyAuth(env.Tip, "订单创建成功"), "data": env.Data, }) } // RechargeTransactions BFF:代理 GET /api/app/coin/trans/list。 // 文档:http://172.104.181.99:25319/swagger/index.html#/金币/get_api_app_coin_trans_list // query:page / pageSize / type(0全部 1充值 2消费 3奖励) func RechargeTransactions(c *gin.Context) { if useragent.TokenFromRequest(c.Request) == "" { c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) return } page := queryInt(c, "page", 1) pageSize := queryInt(c, "pageSize", rechargePageSize) typ := queryIntAllowZero(c, "type", 0) if typ < 0 || typ > 3 { typ = 0 } result, code, err := fetchRechargeTransactions(c, page, pageSize, typ) if err != nil { log.Error("fetch recharge transactions failed", log.E(err)) tip := "交易明细加载失败" if code > 0 { c.JSON(http.StatusOK, gin.H{"code": code, "msg": err.Error(), "tip": tip, "data": nil}) return } c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": tip, "data": nil}) return } if result == nil { result = &rechargeser.CoinTransPage{PageNum: page, PageSize: pageSize} } c.JSON(http.StatusOK, gin.H{ "code": 200, "msg": "ok", "tip": "", "data": gin.H{ "list": rechargeser.BuildTxListFromCoinTrans(result.List, 0), "rawList": result.List, "pageNum": result.PageNum, "pageSize": result.PageSize, "totalCount": result.TotalCount, }, }) } // RechargeCoinTransExport BFF:代理 GET /api/app/coin/trans/export。 // 文档:http://172.104.181.99:25319/swagger/index.html#/金币/get_api_app_coin_trans_export // query:type(0全部 1充值 2消费 3奖励),返回 CSV。 func RechargeCoinTransExport(c *gin.Context) { if useragent.TokenFromRequest(c.Request) == "" { c.JSON(http.StatusOK, gin.H{"code": 401, "msg": "unauthorized", "tip": "请先登录", "data": nil}) return } typ := queryIntAllowZero(c, "type", 0) if typ < 0 || typ > 3 { typ = 0 } result, err := rechargeser.FetchCoinTransExport(c, typ) if err != nil { log.Error("export coin trans failed", log.E(err)) c.JSON(http.StatusOK, gin.H{"code": 500, "msg": err.Error(), "tip": "导出失败,请稍后重试", "data": nil}) return } ct := strings.TrimSpace(result.ContentType) if ct == "" || strings.Contains(strings.ToLower(ct), "json") { ct = "text/csv; charset=utf-8" } disposition := strings.TrimSpace(result.ContentDisposition) if disposition == "" { disposition = `attachment; filename="coin-trans.csv"` } c.Header("Content-Type", ct) c.Header("Content-Disposition", disposition) c.Header("Cache-Control", "no-store") c.Data(http.StatusOK, ct, result.Body) } func fetchRechargeTransactions(c *gin.Context, page, pageSize, typ int) (*rechargeser.CoinTransPage, int, error) { result, code, err := rechargeser.FetchCoinTransPage(c, page, pageSize, typ) if retry, _ := authser.OnSessionExpired(c, code); retry { result, code, err = rechargeser.FetchCoinTransPage(c, page, pageSize, typ) } return result, code, err } // attachRechargeModal 注入充值弹窗数据:GET /api/app/recharge/package/list → coinList。 func attachRechargeModal(c *gin.Context, data gin.H) { if data == nil { return } packages := rechargeser.FetchPackageListOrEmpty(c) vm := rechargeser.BuildDepositPageVM(packages, "coin") data["rechargePackages"] = vm.CoinList data["rechargePayMethods"] = vm.PayMethods data["rechargePackageResponse"] = packages if _, ok := data["unlockCost"]; !ok { data["unlockCost"] = int64(0) } if _, ok := data["balance"]; !ok { data["balance"] = authser.RechargeFromCookies(c) } if _, ok := data["balanceText"]; !ok { data["balanceText"] = authser.FormatRecharge(toInt64(data["balance"])) } } func validCoinPackagePayment(c *gin.Context, packageID int64, payMode string) bool { data, err := rechargeser.FetchPackageList(c) if err != nil || data == nil { return false } for _, item := range data.CoinList { if item.ID != packageID { continue } for _, method := range item.PayMethods { if rechargeser.NormalizePayMode(method.Type) == payMode { return true } } return false } return false } func safePaymentURL(raw string) string { raw = strings.TrimSpace(raw) u, err := url.Parse(raw) if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" { return "" } return raw } func queryInt(c *gin.Context, name string, fallback int) int { v, err := strconv.Atoi(strings.TrimSpace(c.Query(name))) if err != nil || v <= 0 { return fallback } return v } // queryIntAllowZero 允许 0(如 type=0 全部)。 func queryIntAllowZero(c *gin.Context, name string, fallback int) int { raw := strings.TrimSpace(c.Query(name)) if raw == "" { return fallback } v, err := strconv.Atoi(raw) if err != nil { return fallback } return v }