feat: 支付闭环
This commit is contained in:
@@ -8,6 +8,7 @@ type ApiGroup struct {
|
||||
ProgramApi
|
||||
SubscriptionApi
|
||||
InteractionApi
|
||||
PayApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
@@ -18,4 +19,5 @@ var (
|
||||
programService = service.GroupApp.RadioServiceGroup.ProgramService
|
||||
subscriptionService = service.GroupApp.RadioServiceGroup.SubscriptionService
|
||||
interactionService = service.GroupApp.RadioServiceGroup.InteractionService
|
||||
payService = service.GroupApp.RadioServiceGroup.PayService
|
||||
)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package radio
|
||||
|
||||
import (
|
||||
"sundynix-go/global"
|
||||
"sundynix-go/model/commom/response"
|
||||
"sundynix-go/utils/auth"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type PayApi struct{}
|
||||
|
||||
// PrePay 预支付
|
||||
// @Tags 微信支付
|
||||
// @Summary 支付
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param orderId query string true "支付"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"支付成功"}"
|
||||
// @Router /pay/prePay [get]
|
||||
func (a *PayApi) PrePay(c *gin.Context) {
|
||||
orderId := c.Query("orderId")
|
||||
userId := auth.GetUserId(c)
|
||||
res, err := payService.PrePay(orderId, userId)
|
||||
if err != nil {
|
||||
global.Logger.Error("支付失败", zap.Error(err))
|
||||
response.FailWithMsg("支付失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(res, c)
|
||||
}
|
||||
|
||||
// QueryPay 查询支付
|
||||
// @Tags 微信支付
|
||||
// @Summary 支付
|
||||
// @Security BasicAuth
|
||||
// @Produce application/json
|
||||
// @Param orderId query string true "支付"
|
||||
// @Success 200 {string} string "{"success":true,"data":{},"msg":"支付成功"}"
|
||||
// @Router /pay/query [get]
|
||||
func (a *PayApi) QueryPay(c *gin.Context) {
|
||||
outTradeNo := c.Query("outTradeNo")
|
||||
userId := auth.GetUserId(c)
|
||||
res, err := payService.QueryPay(outTradeNo, userId)
|
||||
if err != nil {
|
||||
global.Logger.Error("支付失败", zap.Error(err))
|
||||
response.FailWithMsg("支付失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(res, c)
|
||||
}
|
||||
|
||||
// PayCallback 支付回调
|
||||
// @Tags 微信支付
|
||||
// @Summary 支付回调
|
||||
func (a *PayApi) PayCallback(c *gin.Context) {
|
||||
err := payService.PayCallback(c)
|
||||
if err != nil {
|
||||
global.Logger.Error("支付回调失败", zap.Error(err))
|
||||
response.FailWithMsg("支付回调失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMsg("支付回调成功", c)
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
common "sundynix-go/model/commom/request"
|
||||
"sundynix-go/model/commom/response"
|
||||
"sundynix-go/model/radio/request"
|
||||
radioRes "sundynix-go/model/radio/response"
|
||||
"sundynix-go/utils/auth"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -29,7 +30,6 @@ func (a *SubscriptionApi) GetSubscriptionList(c *gin.Context) {
|
||||
response.FailWithMsg("参数错误: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
list, total, err := subscriptionService.GetUserSubscription(userId, req)
|
||||
if err != nil {
|
||||
global.Logger.Error("获取订阅列表失败!", zap.Error(err))
|
||||
@@ -44,91 +44,30 @@ func (a *SubscriptionApi) GetSubscriptionList(c *gin.Context) {
|
||||
}, c)
|
||||
}
|
||||
|
||||
// CanSubscribe 检查是否可以订阅
|
||||
// UnlockChannel 解锁频道(拉起支付)
|
||||
// @Tags 订阅管理
|
||||
// @Summary 检查是否可以订阅
|
||||
// @Summary 解锁频道
|
||||
// @Param id query string true "id"
|
||||
// @Param eventType query string true "eventType"
|
||||
// @Produce application/json
|
||||
// @Param data body request.SubscribeChannel true "频道ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /radio/subscription/can-subscribe [post]
|
||||
func (a *SubscriptionApi) CanSubscribe(c *gin.Context) {
|
||||
userId := auth.GetUserId(c)
|
||||
var req request.SubscribeChannel
|
||||
// @Router /radio/subscription/unlock [post]
|
||||
func (a *SubscriptionApi) UnlockChannel(c *gin.Context) {
|
||||
var req request.UnlockChannel
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMsg("参数错误: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
can, _, err := subscriptionService.CanSubscribe(userId, req.ChannelId)
|
||||
if err != nil {
|
||||
global.Logger.Error("检查订阅权限失败!", zap.Error(err))
|
||||
response.FailWithMsg(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithData(can, c)
|
||||
}
|
||||
|
||||
// Subscribe 订阅频道
|
||||
// @Tags 订阅管理
|
||||
// @Summary 订阅频道
|
||||
// @Produce application/json
|
||||
// @Param data body request.SubscribeChannel true "频道ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /radio/subscription/subscribe [post]
|
||||
func (a *SubscriptionApi) Subscribe(c *gin.Context) {
|
||||
userId := auth.GetUserId(c)
|
||||
var req request.SubscribeChannel
|
||||
err := c.ShouldBindJSON(&req)
|
||||
res, no, err := subscriptionService.UnlockChannel(userId, req)
|
||||
if err != nil {
|
||||
response.FailWithMsg("参数错误: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
can, reason, err := subscriptionService.CanSubscribe(userId, req.ChannelId)
|
||||
if err != nil {
|
||||
global.Logger.Error("订阅失败!", zap.Error(err))
|
||||
global.Logger.Error("解锁频道失败!", zap.Error(err))
|
||||
response.FailWithMsg(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if !can {
|
||||
response.FailWithMsg(reason, c)
|
||||
return
|
||||
}
|
||||
|
||||
// 订阅类型 1:免费
|
||||
if err := subscriptionService.Subscribe(userId, req.ChannelId, 1); err != nil {
|
||||
global.Logger.Error("订阅失败!", zap.Error(err))
|
||||
response.FailWithMsg(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMsg("订阅成功", c)
|
||||
}
|
||||
|
||||
// Unsubscribe 退订频道
|
||||
// @Tags 订阅管理
|
||||
// @Summary 退订频道
|
||||
// @Produce application/json
|
||||
// @Param data body request.UnsubscribeChannel true "频道ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Router /radio/subscription/unsubscribe [post]
|
||||
func (a *SubscriptionApi) Unsubscribe(c *gin.Context) {
|
||||
userId := auth.GetUserId(c)
|
||||
var req request.UnsubscribeChannel
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMsg("参数错误: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := subscriptionService.Unsubscribe(userId, req.ChannelId); err != nil {
|
||||
global.Logger.Error("退订失败!", zap.Error(err))
|
||||
response.FailWithMsg(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMsg("退订成功", c)
|
||||
response.OkWithData(radioRes.PrePayResult{
|
||||
Payments: res,
|
||||
OutTradeNo: no,
|
||||
}, c)
|
||||
}
|
||||
|
||||
+7
-2
@@ -12,7 +12,12 @@ jwt:
|
||||
issuer: sundynix
|
||||
signing-key: 9149f2eb-d517-4a50-a03a-231dbcf0d872
|
||||
|
||||
# 电台微信小程序
|
||||
# 植趣微信小程序 用于测试支付
|
||||
#mini-program:
|
||||
# app-id: wxb463820bf36dd5d6
|
||||
# app-secret: 731784a74c76c6d31fa00bb847af2c7d
|
||||
|
||||
## 电台微信小程序
|
||||
mini-program:
|
||||
app-id: wx52dfc635739a9c19
|
||||
app-secret: 84c6ddab1f24d0222da57bedb681c81f
|
||||
@@ -25,7 +30,7 @@ wechat-pay:
|
||||
mch-api-v3-key: a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6 # 商户APIv3密钥
|
||||
private-key-path: /Users/blizzard/privateFolder/cert/apiclient_key.pem # 商户APIv3密钥对应的私钥
|
||||
public-key-path: /Users/blizzard/privateFolder/cert/pub_key.pem # 商户APIv3密钥对应的公钥
|
||||
notify-url: https://prod.sundynix.cn/api/wechatpay/notify # 微信支付结果通知回调地址
|
||||
notify-url: https://radio.sundynix.cn/api/wechatpay/notify # 微信支付结果通知回调地址
|
||||
|
||||
# MinIO 对象存储 (音频存储)
|
||||
minio:
|
||||
|
||||
+115
-92
@@ -1022,6 +1022,75 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pay/prePay": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"微信支付"
|
||||
],
|
||||
"summary": "支付",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "支付",
|
||||
"name": "orderId",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{\"success\":true,\"data\":{},\"msg\":\"支付成功\"}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pay/query": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"微信支付"
|
||||
],
|
||||
"summary": "支付",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "支付",
|
||||
"name": "orderId",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{\"success\":true,\"data\":{},\"msg\":\"支付成功\"}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/category/delete": {
|
||||
"post": {
|
||||
"produces": [
|
||||
@@ -1274,6 +1343,39 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/channel/freeList": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"频道管理"
|
||||
],
|
||||
"summary": "获取频道列表",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "分页查询",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.GetChannelList"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/channel/list": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -1797,36 +1899,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/subscription/can-subscribe": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"订阅管理"
|
||||
],
|
||||
"summary": "检查是否可以订阅",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "频道ID",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.SubscribeChannel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/subscription/list": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -1860,54 +1932,29 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/subscription/subscribe": {
|
||||
"post": {
|
||||
"/radio/subscription/unlock": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"订阅管理"
|
||||
],
|
||||
"summary": "订阅频道",
|
||||
"summary": "解锁频道",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "频道ID",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.SubscribeChannel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
"/radio/subscription/unsubscribe": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"订阅管理"
|
||||
],
|
||||
"summary": "退订频道",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "频道ID",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.UnsubscribeChannel"
|
||||
}
|
||||
"type": "string",
|
||||
"description": "eventType",
|
||||
"name": "eventType",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3057,18 +3104,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.SubscribeChannel": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"channelId"
|
||||
],
|
||||
"properties": {
|
||||
"channelId": {
|
||||
"description": "频道ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.ToggleLike": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -3081,18 +3116,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.UnsubscribeChannel": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"channelId"
|
||||
],
|
||||
"properties": {
|
||||
"channelId": {
|
||||
"description": "频道ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.UpdateCategory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
+115
-92
@@ -1015,6 +1015,75 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pay/prePay": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"微信支付"
|
||||
],
|
||||
"summary": "支付",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "支付",
|
||||
"name": "orderId",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{\"success\":true,\"data\":{},\"msg\":\"支付成功\"}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/pay/query": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"微信支付"
|
||||
],
|
||||
"summary": "支付",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "支付",
|
||||
"name": "orderId",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "{\"success\":true,\"data\":{},\"msg\":\"支付成功\"}",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/category/delete": {
|
||||
"post": {
|
||||
"produces": [
|
||||
@@ -1267,6 +1336,39 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/channel/freeList": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"频道管理"
|
||||
],
|
||||
"summary": "获取频道列表",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "分页查询",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.GetChannelList"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/channel/list": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -1790,36 +1892,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/subscription/can-subscribe": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"订阅管理"
|
||||
],
|
||||
"summary": "检查是否可以订阅",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "频道ID",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.SubscribeChannel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/subscription/list": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
@@ -1853,54 +1925,29 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/radio/subscription/subscribe": {
|
||||
"post": {
|
||||
"/radio/subscription/unlock": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"订阅管理"
|
||||
],
|
||||
"summary": "订阅频道",
|
||||
"summary": "解锁频道",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "频道ID",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.SubscribeChannel"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.Response"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
"/radio/subscription/unsubscribe": {
|
||||
"post": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"订阅管理"
|
||||
],
|
||||
"summary": "退订频道",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "频道ID",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/request.UnsubscribeChannel"
|
||||
}
|
||||
"type": "string",
|
||||
"description": "eventType",
|
||||
"name": "eventType",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3050,18 +3097,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.SubscribeChannel": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"channelId"
|
||||
],
|
||||
"properties": {
|
||||
"channelId": {
|
||||
"description": "频道ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.ToggleLike": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -3074,18 +3109,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.UnsubscribeChannel": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"channelId"
|
||||
],
|
||||
"properties": {
|
||||
"channelId": {
|
||||
"description": "频道ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"request.UpdateCategory": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
+75
-62
@@ -381,14 +381,6 @@ definitions:
|
||||
- channelId
|
||||
- title
|
||||
type: object
|
||||
request.SubscribeChannel:
|
||||
properties:
|
||||
channelId:
|
||||
description: 频道ID
|
||||
type: string
|
||||
required:
|
||||
- channelId
|
||||
type: object
|
||||
request.ToggleLike:
|
||||
properties:
|
||||
programId:
|
||||
@@ -397,14 +389,6 @@ definitions:
|
||||
required:
|
||||
- programId
|
||||
type: object
|
||||
request.UnsubscribeChannel:
|
||||
properties:
|
||||
channelId:
|
||||
description: 频道ID
|
||||
type: string
|
||||
required:
|
||||
- channelId
|
||||
type: object
|
||||
request.UpdateCategory:
|
||||
properties:
|
||||
coverId:
|
||||
@@ -1261,6 +1245,48 @@ paths:
|
||||
summary: 文件上传
|
||||
tags:
|
||||
- 文件相关
|
||||
/pay/prePay:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: 支付
|
||||
in: query
|
||||
name: orderId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: '{"success":true,"data":{},"msg":"支付成功"}'
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: 支付
|
||||
tags:
|
||||
- 微信支付
|
||||
/pay/query:
|
||||
get:
|
||||
parameters:
|
||||
- description: 支付
|
||||
in: query
|
||||
name: orderId
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: '{"success":true,"data":{},"msg":"支付成功"}'
|
||||
schema:
|
||||
type: string
|
||||
security:
|
||||
- BasicAuth: []
|
||||
summary: 支付
|
||||
tags:
|
||||
- 微信支付
|
||||
/radio/category/delete:
|
||||
post:
|
||||
parameters:
|
||||
@@ -1422,6 +1448,27 @@ paths:
|
||||
summary: 获取频道详情
|
||||
tags:
|
||||
- 频道管理
|
||||
/radio/channel/freeList:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: 分页查询
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.GetChannelList'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
summary: 获取频道列表
|
||||
tags:
|
||||
- 频道管理
|
||||
/radio/channel/list:
|
||||
post:
|
||||
consumes:
|
||||
@@ -1754,25 +1801,6 @@ paths:
|
||||
summary: 更新节目
|
||||
tags:
|
||||
- 节目管理
|
||||
/radio/subscription/can-subscribe:
|
||||
post:
|
||||
parameters:
|
||||
- description: 频道ID
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SubscribeChannel'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
summary: 检查是否可以订阅
|
||||
tags:
|
||||
- 订阅管理
|
||||
/radio/subscription/list:
|
||||
post:
|
||||
consumes:
|
||||
@@ -1794,15 +1822,19 @@ paths:
|
||||
summary: 获取我的订阅列表
|
||||
tags:
|
||||
- 订阅管理
|
||||
/radio/subscription/subscribe:
|
||||
post:
|
||||
/radio/subscription/unlock:
|
||||
get:
|
||||
parameters:
|
||||
- description: 频道ID
|
||||
in: body
|
||||
name: data
|
||||
- description: id
|
||||
in: query
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.SubscribeChannel'
|
||||
type: string
|
||||
- description: eventType
|
||||
in: query
|
||||
name: eventType
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
@@ -1810,26 +1842,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
summary: 订阅频道
|
||||
tags:
|
||||
- 订阅管理
|
||||
/radio/subscription/unsubscribe:
|
||||
post:
|
||||
parameters:
|
||||
- description: 频道ID
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/request.UnsubscribeChannel'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.Response'
|
||||
summary: 退订频道
|
||||
summary: 解锁频道
|
||||
tags:
|
||||
- 订阅管理
|
||||
/role/delete:
|
||||
|
||||
@@ -44,6 +44,8 @@ func MigrateTable() {
|
||||
radio.RadioProgram{},
|
||||
radio.RadioSubscription{},
|
||||
radio.RadioUser{},
|
||||
radio.Order{},
|
||||
radio.PayNotify{},
|
||||
)
|
||||
if err != nil {
|
||||
global.Logger.Error("Migrate table failed,err:", zap.Error(err))
|
||||
|
||||
@@ -52,6 +52,7 @@ func Routers() {
|
||||
radioRouter.InitChannelRouter(NeedAuthGroup) //频道相关
|
||||
radioRouter.InitProgramRouter(NeedAuthGroup) //节目相关
|
||||
radioRouter.InitSubscriptionRouter(NeedAuthGroup) //订阅相关
|
||||
radioRouter.InitPayRouter(NeedAuthGroup, PublicGroup) //支付和回调
|
||||
radioRouter.InitInteractionRouter(NeedAuthGroup) //用户互动相关
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package radio
|
||||
|
||||
import (
|
||||
"sundynix-go/global"
|
||||
"sundynix-go/model/system"
|
||||
)
|
||||
|
||||
// RadioCategory 电台分类表
|
||||
@@ -10,10 +9,6 @@ type RadioCategory struct {
|
||||
global.BaseModel
|
||||
Name string `gorm:"size:50" json:"name"` // 分类名称
|
||||
Description string `gorm:"size:255" json:"description"` // 分类描述
|
||||
IconId string `gorm:"size:50" json:"iconId"` // 图标OSS ID
|
||||
Icon *system.Oss `gorm:"foreignKey:IconId" json:"icon"` // 图标OSS
|
||||
CoverId string `gorm:"size:50" json:"coverId"` // 封面图OSS ID
|
||||
Cover *system.Oss `gorm:"foreignKey:CoverId" json:"cover"` // 封面图OSS
|
||||
Sort int `gorm:"default:0" json:"sort"` // 排序
|
||||
Status int `gorm:"default:1" json:"status"` // 状态 0:禁用 1:启用
|
||||
Channels []*RadioChannel `gorm:"foreignKey:CategoryId" json:"channels"`
|
||||
|
||||
@@ -2,7 +2,7 @@ package radio
|
||||
|
||||
import (
|
||||
"sundynix-go/global"
|
||||
"sundynix-go/model/system"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RadioChannel 电台频道表
|
||||
@@ -16,12 +16,12 @@ type RadioChannel struct {
|
||||
MonthlyPrice int `gorm:"default:0;comment:价格,单位,分 " json:"monthlyPrice"` //包月价格 单位:分
|
||||
QuarterlyPrice int `gorm:"default:0;comment:价格,单位,分 " json:"quarterlyPrice"` //包季度价格 单位:分
|
||||
AnnualPrice int `gorm:"default:0;comment:价格,单位,分 " json:"annualPrice"` //包年价格 单位:分
|
||||
CoverId string `gorm:"size:50" json:"coverId"` // 封面图OSS ID
|
||||
Cover *system.Oss `gorm:"foreignKey:CoverId" json:"cover"` // 封面图OSS
|
||||
Cover string `gorm:"size:100" json:"cover"` // 封面emoji
|
||||
Tags string `gorm:"size:255" json:"tags"` // 标签,逗号分隔
|
||||
Sort int `gorm:"default:0" json:"sort"` // 排序
|
||||
Status int `gorm:"default:1" json:"status"` // 状态 0:禁用 1:启用
|
||||
HasSubscribed int `gorm:"-" json:"hasSubscribed"` // 状态 0:未订阅 1:已订阅
|
||||
ExpiredAt *time.Time `gorm:"-" json:"expiredAt"` // 新增:过期时间,使用指针方便处理 null
|
||||
Programs []*RadioProgram `gorm:"foreignKey:ChannelId" json:"programs"` //频道下的节目
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package radio
|
||||
|
||||
import "sundynix-go/global"
|
||||
|
||||
type PayNotify struct {
|
||||
global.BaseModel
|
||||
Amount int64 `json:"amount" gorm:"column:amount"`
|
||||
Currency string `json:"currency" gorm:"column:currency"`
|
||||
PayerCurrency string `json:"payer_currency" gorm:"column:payer_currency"`
|
||||
PayerTotal int64 `json:"payer_total" gorm:"column:payer_total"`
|
||||
Appid string `json:"appId" gorm:"column:app_id"`
|
||||
Attach string `json:"attach" gorm:"column:attach"`
|
||||
BankType string `json:"bank_type" gorm:"column:bank_type"`
|
||||
MchId string `json:"mchId" gorm:"column:mch_id"`
|
||||
OutTradeNo string `json:"out_trade_no" gorm:"column:out_trade_no"`
|
||||
Payer string `json:"payer" gorm:"column:out_trade_no"`
|
||||
SuccessTime string `json:"success_time" gorm:"column:success_time"`
|
||||
TradeState string `json:"trade_state" gorm:"column:trade_state"`
|
||||
TradeStateDesc string `json:"trade_state_desc" gorm:"column:trade_state_desc"`
|
||||
TradeType string `json:"trade_type" gorm:"column:trade_type"`
|
||||
TransactionId string `json:"transaction_id" gorm:"column:transaction_id"`
|
||||
}
|
||||
@@ -12,8 +12,7 @@ type RadioProgram struct {
|
||||
Title string `gorm:"size:100" json:"title"` // 节目标题
|
||||
Description string `gorm:"size:500" json:"description"` // 节目描述
|
||||
Content string `gorm:"type:text" json:"content"`
|
||||
CoverId string `gorm:"size:50" json:"coverId"` // 封面图OSS ID
|
||||
Cover *system.Oss `gorm:"foreignKey:CoverId" json:"cover"` // 封面图OSS
|
||||
Cover string `gorm:"size:100" json:"cover"` // 封面图emoji
|
||||
AudioId string `gorm:"size:50" json:"audioId"` // 音频OSS ID
|
||||
Audio *system.Oss `gorm:"foreignKey:AudioId" json:"audio"` // 音频OSS
|
||||
Duration int `gorm:"default:0" json:"duration"` // 时长(秒)
|
||||
|
||||
@@ -2,14 +2,16 @@ package radio
|
||||
|
||||
import (
|
||||
"sundynix-go/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RadioSubscription 用户订阅表
|
||||
type RadioSubscription struct {
|
||||
global.BaseModel
|
||||
UserId string `gorm:"size:50;index;not null;uniqueIndex:idx_user_channel" json:"userId"` // 用户ID
|
||||
ChannelId string `gorm:"size:50;index;uniqueIndex:idx_user_channel" json:"channelId"` // 频道ID
|
||||
Status int `gorm:"type:tinyint;default:1"` //1-订阅中,2-已取消
|
||||
UserId string `gorm:"size:50;index;not null;" json:"userId"` // 用户ID
|
||||
ChannelId string `gorm:"size:50;index;" json:"channelId"` // 频道ID
|
||||
ExpiredAt time.Time `gorm:"index"`
|
||||
Status int `gorm:"type:tinyint;default:1"` //1-订阅中,2-已到期
|
||||
Channel *RadioChannel `gorm:"foreignKey:ChannelId" json:"channel"`
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package radio
|
||||
|
||||
import (
|
||||
"sundynix-go/global"
|
||||
"sundynix-go/model/system"
|
||||
)
|
||||
|
||||
type Order struct {
|
||||
global.BaseModel
|
||||
OutTradeNo string `json:"outTradeNo" gorm:"index;column:out_trade_no;comment:商户订单号"`
|
||||
UserId string `json:"userId" form:"userId" gorm:"index;column:user_id;comment:用户id"`
|
||||
ChannelId string `gorm:"column:channel_id"`
|
||||
SubscriptionType string `gorm:"column:sub_type;comment:1:月,2:季,3:年"`
|
||||
Name string `json:"name" gorm:"column:name;comment:订单名称"`
|
||||
Amount int `json:"amount" gorm:"column:amount;comment:金额分"`
|
||||
Status int `json:"status" gorm:"column:status;comment:订单状态"` // 0:待支付 1:已支付 2:已关闭
|
||||
PayStatus string `json:"payStatus" gorm:"column:pay_status;comment:支付状态"`
|
||||
User *system.User `json:"user" gorm:"foreignKey:UserId"`
|
||||
}
|
||||
@@ -13,8 +13,6 @@ type GetCategoryList struct {
|
||||
type SaveCategory struct {
|
||||
Name string `json:"name" binding:"required"` // 分类名称
|
||||
Description string `json:"description"` // 分类描述
|
||||
IconId string `json:"iconId"` // 图标URL
|
||||
CoverId string `json:"coverId"` // 封面图URL
|
||||
Sort int `json:"sort"` // 排序
|
||||
Status int `json:"status"` // 状态
|
||||
}
|
||||
@@ -24,8 +22,6 @@ type UpdateCategory struct {
|
||||
Id string `json:"id" binding:"required"` // 分类ID
|
||||
Name string `json:"name"` // 分类名称
|
||||
Description string `json:"description"` // 分类描述
|
||||
IconId string `json:"iconId"` // 图标URL
|
||||
CoverId string `json:"coverId"` // 封面图URL
|
||||
Sort int `json:"sort"` // 排序
|
||||
Status int `json:"status"` // 状态
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type SaveChannel struct {
|
||||
CategoryId string `json:"categoryId" binding:"required"` // 分类ID
|
||||
Name string `json:"name" binding:"required"` // 频道名称
|
||||
Description string `json:"description"` // 频道描述
|
||||
CoverId string `json:"coverId"` // 封面图URL
|
||||
Cover string `json:"cover"` // 封面图URL
|
||||
Tags string `json:"tags"` // 标签
|
||||
IsFree int `json:"isFree"` //是否永久免费
|
||||
IsVipOnly int `json:"isVipOnly"` //是否vip专享
|
||||
@@ -32,7 +32,7 @@ type UpdateChannel struct {
|
||||
CategoryId string `json:"categoryId"` // 分类ID
|
||||
Name string `json:"name"` // 频道名称
|
||||
Description string `json:"description"` // 频道描述
|
||||
CoverId string `json:"coverId"` // 封面图URL
|
||||
Cover string `json:"cover"` // 封面图URL
|
||||
Tags string `json:"tags"` // 标签
|
||||
IsFree int `json:"isFree"`
|
||||
IsVipOnly int `json:"isVipOnly"` //是否vip专享
|
||||
@@ -42,3 +42,8 @@ type UpdateChannel struct {
|
||||
Sort int `json:"sort"` // 排序
|
||||
Status int `json:"status"` // 状态
|
||||
}
|
||||
|
||||
type UnlockChannel struct {
|
||||
ChannelId string `json:"channelId" binding:"required"`
|
||||
EventType string `json:"type" binding:"required"` // 1 月 2 季 3 年
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ type SaveProgram struct {
|
||||
Title string `json:"title" binding:"required"` // 节目标题
|
||||
Description string `json:"description"` // 节目描述
|
||||
Content string `json:"content"`
|
||||
CoverId string `json:"coverId"` // 封面图URL
|
||||
Cover string `json:"cover"` // 封面图URL
|
||||
AudioId string `json:"audioId"` // 音频URL
|
||||
Duration int `json:"duration"` // 时长(秒)
|
||||
Tags string `json:"tags"` // 标签
|
||||
@@ -30,7 +30,7 @@ type UpdateProgram struct {
|
||||
Title string `json:"title"` // 节目标题
|
||||
Description string `json:"description"` // 节目描述
|
||||
Content string `json:"content"`
|
||||
CoverId string `json:"coverId"` // 封面图URL
|
||||
Cover string `json:"cover"` // 封面图URL
|
||||
AudioId string `json:"audioId"` // 音频URL
|
||||
Duration int `json:"duration"` // 时长(秒)
|
||||
Tags string `json:"tags"` // 标签
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package response
|
||||
|
||||
import "sundynix-go/model/radio"
|
||||
|
||||
// CategoryResponse 分类响应
|
||||
type CategoryResponse struct {
|
||||
Id string `json:"id"`
|
||||
@@ -23,21 +21,3 @@ type CategoryDetailResponse struct {
|
||||
Sort int `json:"sort"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// ToCategoryResponse 转换为分类响应
|
||||
func ToCategoryResponse(category *radio.RadioCategory) CategoryResponse {
|
||||
resp := CategoryResponse{
|
||||
Id: category.Id,
|
||||
Name: category.Name,
|
||||
Description: category.Description,
|
||||
Sort: category.Sort,
|
||||
Status: category.Status,
|
||||
}
|
||||
if category.Icon != nil {
|
||||
resp.Icon = category.Icon.Url
|
||||
}
|
||||
if category.Cover != nil {
|
||||
resp.CoverUrl = category.Cover.Url
|
||||
}
|
||||
return resp
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package response
|
||||
|
||||
import "github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
|
||||
type PrePayResult struct {
|
||||
Payments *jsapi.PrepayWithRequestPaymentResponse `json:"payments"`
|
||||
OutTradeNo string `json:"outTradeNo"`
|
||||
}
|
||||
@@ -8,6 +8,7 @@ type RadioRouterGroup struct {
|
||||
ProgramRouter
|
||||
SubscriptionRouter
|
||||
InteractionRouter
|
||||
PayRouter
|
||||
}
|
||||
|
||||
var GroupApp = new(RadioRouterGroup)
|
||||
@@ -18,4 +19,5 @@ var (
|
||||
programApi = v1.ApiGroupApp.RadioApiGroup.ProgramApi
|
||||
subscriptionApi = v1.ApiGroupApp.RadioApiGroup.SubscriptionApi
|
||||
interactionApi = v1.ApiGroupApp.RadioApiGroup.InteractionApi
|
||||
payApi = v1.ApiGroupApp.RadioApiGroup.PayApi
|
||||
)
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package radio
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type PayRouter struct{}
|
||||
|
||||
func (c *PayRouter) InitPayRouter(PrivateRouter *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
payRouter := PrivateRouter.Group("pay")
|
||||
payCallbackRouter := PublicRouter.Group("wechatpay")
|
||||
{
|
||||
payRouter.GET("prePay", payApi.PrePay)
|
||||
payRouter.GET("query", payApi.QueryPay)
|
||||
payCallbackRouter.POST("notify", payApi.PayCallback) //支付结果回调
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,8 +10,6 @@ func (r *SubscriptionRouter) InitSubscriptionRouter(Router *gin.RouterGroup) {
|
||||
subscriptionRouter := Router.Group("radio/subscription")
|
||||
{
|
||||
subscriptionRouter.POST("list", subscriptionApi.GetSubscriptionList)
|
||||
subscriptionRouter.POST("can-subscribe", subscriptionApi.CanSubscribe)
|
||||
subscriptionRouter.POST("subscribe", subscriptionApi.Subscribe)
|
||||
subscriptionRouter.POST("unsubscribe", subscriptionApi.Unsubscribe)
|
||||
subscriptionRouter.POST("unlock", subscriptionApi.UnlockChannel)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +69,6 @@ func (s *CategoryService) SaveCategory(req radioReq.SaveCategory) error {
|
||||
category := radio.RadioCategory{
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
IconId: req.IconId,
|
||||
CoverId: req.CoverId,
|
||||
Sort: req.Sort,
|
||||
Status: req.Status,
|
||||
}
|
||||
@@ -82,8 +80,6 @@ func (s *CategoryService) UpdateCategory(req radioReq.UpdateCategory) error {
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"icon_id": req.IconId,
|
||||
"cover_id": req.CoverId,
|
||||
"sort": req.Sort,
|
||||
"status": req.Status,
|
||||
}
|
||||
|
||||
@@ -6,7 +6,9 @@ import (
|
||||
common "sundynix-go/model/commom/request"
|
||||
"sundynix-go/model/radio"
|
||||
radioReq "sundynix-go/model/radio/request"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -55,22 +57,56 @@ func (s *ChannelService) GetChannelList(userId string, info radioReq.GetChannelL
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
//查询用户的订阅频道
|
||||
now := time.Now()
|
||||
var subIds []string
|
||||
err = global.DB.Model(&radio.RadioSubscription{}).
|
||||
Where("user_id = ?", userId).
|
||||
Where("status = ?", 1). // 建议将常量也参数化,提高安全性
|
||||
Where("expired_at > ?", now).
|
||||
Pluck("channel_id", &subIds).
|
||||
Error
|
||||
// 使用map
|
||||
subMap := make(map[string]bool)
|
||||
for _, id := range subIds {
|
||||
subMap[id] = true
|
||||
}
|
||||
for i := range list {
|
||||
list[i].HasSubscribed = 0
|
||||
if subMap[list[i].Id] {
|
||||
list[i].HasSubscribed = 1
|
||||
}
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (s *ChannelService) GetAllChannelList(categoryId, userId string) ([]radio.RadioChannel, error) {
|
||||
var res []radio.RadioChannel
|
||||
err := global.DB.Where("category_id = ?", categoryId).Find(&res).Preload("Cover").Error
|
||||
return res, err
|
||||
}
|
||||
|
||||
// GetChannelById 获取频道详情
|
||||
func (s *ChannelService) GetChannelById(userId, id string) (radio.RadioChannel, error) {
|
||||
var channel radio.RadioChannel
|
||||
err := global.DB.Where("id = ?", id).Preload("Cover").First(&channel).Error
|
||||
err := global.DB.Where("id = ?", id).First(&channel).Error
|
||||
if err != nil {
|
||||
return channel, err
|
||||
}
|
||||
if channel.IsFree == 1 {
|
||||
return channel, nil
|
||||
}
|
||||
channel.HasSubscribed = 0
|
||||
if userId != "" {
|
||||
var sub radio.RadioSubscription
|
||||
err = global.DB.Model(&radio.RadioSubscription{}).
|
||||
Where("user_id = ?", userId).
|
||||
Where("channel_id = ?", id).
|
||||
Where("status = ?", 1).
|
||||
Where("expired_at > ?", time.Now()).
|
||||
First(&sub).Error
|
||||
if err != nil {
|
||||
// 记录日志但不返回错误,避免影响主流程
|
||||
global.Logger.Warn("query subscription status failed", zap.Error(err))
|
||||
return channel, nil
|
||||
}
|
||||
channel.HasSubscribed = 1
|
||||
channel.ExpiredAt = &sub.ExpiredAt
|
||||
}
|
||||
return channel, nil
|
||||
}
|
||||
|
||||
@@ -80,7 +116,7 @@ func (s *ChannelService) SaveChannel(req radioReq.SaveChannel) error {
|
||||
CategoryId: req.CategoryId,
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
CoverId: req.CoverId,
|
||||
Cover: req.Cover,
|
||||
Tags: req.Tags,
|
||||
IsVipOnly: req.IsVipOnly,
|
||||
MonthlyPrice: req.MonthlyPrice,
|
||||
@@ -98,7 +134,7 @@ func (s *ChannelService) UpdateChannel(req radioReq.UpdateChannel) error {
|
||||
"category_id": req.CategoryId,
|
||||
"name": req.Name,
|
||||
"description": req.Description,
|
||||
"cover_id": req.CoverId,
|
||||
"cover": req.Cover,
|
||||
"tags": req.Tags,
|
||||
"is_vip_only": req.IsVipOnly,
|
||||
"monthly_price": req.MonthlyPrice,
|
||||
|
||||
@@ -6,6 +6,8 @@ type ServiceGroup struct {
|
||||
ProgramService
|
||||
SubscriptionService
|
||||
InteractionService
|
||||
PayService
|
||||
OrderService
|
||||
}
|
||||
|
||||
var GroupApp = new(ServiceGroup)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package radio
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sundynix-go/model/radio"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OrderService struct{}
|
||||
|
||||
var OrderServiceApp = new(OrderService)
|
||||
|
||||
// ExecuteOrderUnlock 核心原子操作:解锁权限
|
||||
func (s *OrderService) ExecuteOrderUnlock(tx *gorm.DB, outTradeNo string) error {
|
||||
var order radio.Order
|
||||
// 1. 锁住订单行,防止回调和主动查询并发导致时长翻倍
|
||||
if err := tx.Set("gorm:query_option", "FOR UPDATE").
|
||||
Where("out_trade_no = ?", outTradeNo).First(&order).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 幂等检查
|
||||
if order.Status == 1 {
|
||||
return nil // 已处理,直接返回
|
||||
}
|
||||
|
||||
// 3. 更新订单状态
|
||||
if err := tx.Model(&order).Updates(map[string]interface{}{
|
||||
"status": 1,
|
||||
"pay_status": "SUCCESS",
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 根据订单中的 sub_type 决定增加几个月
|
||||
var months int
|
||||
switch order.SubscriptionType {
|
||||
case "1":
|
||||
months = 1 // 月
|
||||
case "2":
|
||||
months = 3 // 季
|
||||
case "3":
|
||||
months = 12 // 年
|
||||
}
|
||||
|
||||
// 5. 更新或创建订阅权限
|
||||
var sub radio.RadioSubscription
|
||||
now := time.Now()
|
||||
err := tx.Where("user_id = ? AND channel_id = ?", order.UserId, order.ChannelId).First(&sub).Error
|
||||
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// 首次订阅
|
||||
return tx.Create(&radio.RadioSubscription{
|
||||
UserId: order.UserId,
|
||||
ChannelId: order.ChannelId,
|
||||
ExpiredAt: now.AddDate(0, months, 0),
|
||||
Status: 1,
|
||||
}).Error
|
||||
} else {
|
||||
// 续费逻辑
|
||||
newExpiredAt := sub.ExpiredAt
|
||||
if sub.ExpiredAt.Before(now) {
|
||||
newExpiredAt = now // 已过期,从现在开始往后加
|
||||
}
|
||||
return tx.Model(&sub).Updates(map[string]interface{}{
|
||||
"expired_at": newExpiredAt.AddDate(0, months, 0),
|
||||
"status": 1,
|
||||
}).Error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package radio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sundynix-go/global"
|
||||
"sundynix-go/model/radio"
|
||||
"sundynix-go/model/system"
|
||||
"sundynix-go/utils/wechat"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/auth/verifiers"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core/notify"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/utils"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type PayService struct{}
|
||||
|
||||
var PayServiceApp = new(PayService)
|
||||
|
||||
// PrePay 预支付
|
||||
func (s *PayService) PrePay(orderId, userId string) (resp *jsapi.PrepayWithRequestPaymentResponse, err error) {
|
||||
//1.查询订单和 用户
|
||||
var order radio.Order
|
||||
err = global.DB.Where("id = ?", orderId).First(&order).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var user system.User
|
||||
err = global.DB.Where("id = ?", userId).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payClient, err := wechat.GetWxPayClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svc := jsapi.JsapiApiService{Client: payClient}
|
||||
result, _, err := svc.PrepayWithRequestPayment(context.Background(),
|
||||
jsapi.PrepayRequest{
|
||||
Appid: core.String(global.Config.MiniProgram.AppId),
|
||||
Mchid: core.String(global.Config.WechatPay.MchId),
|
||||
Description: core.String(order.Name),
|
||||
OutTradeNo: core.String(order.OutTradeNo),
|
||||
//TimeExpire: core.Time(time.Now()), //选填
|
||||
//Attach: core.String("自定义数据说明"), //选填
|
||||
NotifyUrl: core.String(global.Config.WechatPay.NotifyUrl),
|
||||
//GoodsTag: core.String("WXG"), //选填
|
||||
//SupportFapiao: core.Bool(false), //选填
|
||||
Amount: &jsapi.Amount{
|
||||
Currency: core.String("CNY"),
|
||||
Total: core.Int64(int64(order.Amount)),
|
||||
},
|
||||
Payer: &jsapi.Payer{
|
||||
Openid: core.String(user.MiniOpenId),
|
||||
},
|
||||
//Detail: &jsapi.Detail{
|
||||
// CostPrice: core.Int64(608800),
|
||||
// GoodsDetail: []jsapi.GoodsDetail{jsapi.GoodsDetail{
|
||||
// GoodsName: core.String("iPhoneX 256G"),
|
||||
// MerchantGoodsId: core.String("ABC"),
|
||||
// Quantity: core.Int64(1),
|
||||
// UnitPrice: core.Int64(828800),
|
||||
// WechatpayGoodsId: core.String("1001"),
|
||||
// }},
|
||||
// InvoiceId: core.String("wx123"),
|
||||
//}, //选填
|
||||
//SceneInfo: &jsapi.SceneInfo{
|
||||
// DeviceId: core.String("013467007045764"),
|
||||
// PayerClientIp: core.String("14.23.150.211"),
|
||||
// StoreInfo: &jsapi.StoreInfo{
|
||||
// Address: core.String("广东省深圳市南山区科技中一道10000号"),
|
||||
// AreaCode: core.String("440305"),
|
||||
// Id: core.String("0001"),
|
||||
// Name: core.String("腾讯大厦分店"),
|
||||
// },
|
||||
//},
|
||||
//SettleInfo: &jsapi.SettleInfo{
|
||||
// ProfitSharing: core.Bool(false),
|
||||
//}, //选填
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
// PayCallback 支付回调
|
||||
func (s *PayService) PayCallback(c *gin.Context) error {
|
||||
//1.加载共钥
|
||||
mchPublicKeyPath := global.Config.WechatPay.PublicKeyPath
|
||||
mchPublicKey, err := utils.LoadPublicKeyWithPath(mchPublicKeyPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := context.Background()
|
||||
//2.创建客户端
|
||||
handler, err := notify.NewRSANotifyHandler(global.Config.WechatPay.MchAPIv3Key,
|
||||
verifiers.NewSHA256WithRSAPubkeyVerifier(global.Config.WechatPay.PublicKeyId, *mchPublicKey))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
//3.验签 解密
|
||||
//将支付回调通知中的内容,解析为 payments.Transaction。
|
||||
transaction := new(payments.Transaction)
|
||||
notifyReq, err := handler.ParseNotifyRequest(ctx, c.Request, transaction)
|
||||
// 4.如果验签未通过,或者解密失败
|
||||
if err != nil {
|
||||
//应答微信
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
|
||||
"code": "FAIL",
|
||||
"message": "失败",
|
||||
})
|
||||
global.Logger.Error("wxPay回调-验签或解密:", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
//5.应答微信
|
||||
c.Status(http.StatusOK)
|
||||
// 6.处理通知内容
|
||||
global.Logger.Info("wxPay回调-成功:", zap.Any("notifyReq", notifyReq.Summary))
|
||||
//7. 异步处理数据
|
||||
go func() {
|
||||
err = global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
//7.1 回调记录
|
||||
payNotify := radio.PayNotify{
|
||||
Amount: *transaction.Amount.Total,
|
||||
Currency: *transaction.Amount.Currency,
|
||||
PayerCurrency: *transaction.Amount.PayerCurrency,
|
||||
PayerTotal: *transaction.Amount.PayerTotal,
|
||||
Appid: *transaction.Appid,
|
||||
MchId: *transaction.Mchid,
|
||||
OutTradeNo: *transaction.OutTradeNo,
|
||||
Attach: *transaction.Attach,
|
||||
BankType: *transaction.BankType,
|
||||
Payer: *transaction.Payer.Openid,
|
||||
SuccessTime: *transaction.SuccessTime,
|
||||
TradeState: *transaction.TradeState,
|
||||
TradeStateDesc: *transaction.TradeStateDesc,
|
||||
TradeType: *transaction.TradeType,
|
||||
TransactionId: *transaction.TransactionId,
|
||||
}
|
||||
err = global.DB.Create(&payNotify).Error
|
||||
if err != nil {
|
||||
global.Logger.Error("wxPay回调-存储数据异常:", zap.Error(err))
|
||||
}
|
||||
if payNotify.TradeState == "SUCCESS" {
|
||||
return OrderServiceApp.ExecuteOrderUnlock(tx, *transaction.OutTradeNo)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
}()
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
// QueryPay 根据商户订单号查询订单状态
|
||||
func (s *PayService) QueryPay(no string, userId string) (bool, error) {
|
||||
var order radio.Order
|
||||
if err := global.DB.Where("out_trade_no = ?", no).First(&order).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
// 1. 如果本地已经成功,直接返回成功
|
||||
if order.Status == 1 {
|
||||
return true, nil
|
||||
}
|
||||
//2.本地还是待支付,主动调用微信 API 查询
|
||||
payClient, err := wechat.GetWxPayClient()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
svc := jsapi.JsapiApiService{Client: payClient}
|
||||
resp, _, err := svc.QueryOrderByOutTradeNo(context.Background(), jsapi.QueryOrderByOutTradeNoRequest{
|
||||
OutTradeNo: core.String(no),
|
||||
Mchid: core.String(global.Config.WechatPay.MchId),
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
global.Logger.Info("查询订单状态:", zap.Any("resp", resp))
|
||||
state := *resp.TradeState
|
||||
outTradeNo := *resp.OutTradeNo
|
||||
if state == "SUCCESS" {
|
||||
// 3. 微信那边付过了,本地还没解锁,立即执行事务
|
||||
err = global.DB.Transaction(func(tx *gorm.DB) error {
|
||||
return OrderServiceApp.ExecuteOrderUnlock(tx, outTradeNo)
|
||||
})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
@@ -12,7 +12,7 @@ type ProgramService struct{}
|
||||
|
||||
// GetProgramList 获取节目列表
|
||||
func (s *ProgramService) GetProgramList(info radioReq.GetProgramList) ([]radio.RadioProgram, int64, error) {
|
||||
db := global.DB.Model(&radio.RadioProgram{}).Preload("Cover").Preload("Audio")
|
||||
db := global.DB.Model(&radio.RadioProgram{}).Preload("Audio")
|
||||
var list []radio.RadioProgram
|
||||
var total int64
|
||||
|
||||
@@ -39,18 +39,22 @@ func (s *ProgramService) GetProgramList(info radioReq.GetProgramList) ([]radio.R
|
||||
// GetProgramById 获取节目详情
|
||||
func (s *ProgramService) GetProgramById(id string) (*radio.RadioProgram, error) {
|
||||
var program radio.RadioProgram
|
||||
err := global.DB.Where("id = ?", id).Preload("Cover").Preload("Audio").First(&program).Error
|
||||
err := global.DB.Where("id = ?", id).Preload("Audio").First(&program).Error
|
||||
return &program, err
|
||||
}
|
||||
|
||||
// SaveProgram 保存节目
|
||||
func (s *ProgramService) SaveProgram(req radioReq.SaveProgram) error {
|
||||
var channel radio.RadioChannel
|
||||
if err := global.DB.Where("id = ?", req.ChannelId).First(&channel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
program := radio.RadioProgram{
|
||||
ChannelId: req.ChannelId,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Content: req.Content,
|
||||
CoverId: req.CoverId,
|
||||
Cover: channel.Cover,
|
||||
AudioId: req.AudioId,
|
||||
Duration: req.Duration,
|
||||
Tags: req.Tags,
|
||||
@@ -66,7 +70,7 @@ func (s *ProgramService) UpdateProgram(req radioReq.UpdateProgram) error {
|
||||
"title": req.Title,
|
||||
"description": req.Description,
|
||||
"content": req.Content,
|
||||
"cover_id": req.CoverId,
|
||||
"cover": req.Cover,
|
||||
"audio_id": req.AudioId,
|
||||
"duration": req.Duration,
|
||||
"tags": req.Tags,
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package radio
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sundynix-go/global"
|
||||
common "sundynix-go/model/commom/request"
|
||||
"sundynix-go/model/radio"
|
||||
"time"
|
||||
"sundynix-go/model/radio/request"
|
||||
"sundynix-go/utils/uniqueid"
|
||||
"sundynix-go/utils/wechat"
|
||||
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/core"
|
||||
"github.com/wechatpay-apiv3/wechatpay-go/services/payments/jsapi"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SubscriptionService struct{}
|
||||
|
||||
const MaxFreeSubscription = 2
|
||||
|
||||
// GetUserSubscription 获取用户订阅列表
|
||||
func (s *SubscriptionService) GetUserSubscription(userId string, info common.PageInfo) ([]radio.RadioSubscription, int64, error) {
|
||||
var subscriptions []radio.RadioSubscription
|
||||
@@ -34,7 +37,6 @@ func (s *SubscriptionService) GetUserSubscription(userId string, info common.Pag
|
||||
Order("created_at DESC").
|
||||
// 级联加载频道及其封面
|
||||
Preload("Channel").
|
||||
Preload("Channel.Cover").
|
||||
// 关键:子查询过滤——只预加载每个频道 ID 最大的那一条节目
|
||||
Preload("Channel.Programs", func(db *gorm.DB) *gorm.DB {
|
||||
// 子查询:找到每个频道下 ID 最大的节目(通常 ID 越大代表越新,也可以用 CreatedAt)
|
||||
@@ -44,9 +46,7 @@ func (s *SubscriptionService) GetUserSubscription(userId string, info common.Pag
|
||||
Where("created_at = (SELECT MAX(created_at) FROM sundynix_radio_program AS rp WHERE rp.channel_id = sundynix_radio_program.channel_id AND rp.status = 1)")
|
||||
|
||||
// 嵌套预加载:节目里的音频和封面也一并带出来
|
||||
return db.Where("id IN (?)", subQuery).
|
||||
Preload("Cover").
|
||||
Preload("Audio")
|
||||
return db.Where("id IN (?)", subQuery).Preload("Audio")
|
||||
}).
|
||||
Find(&subscriptions).Error
|
||||
|
||||
@@ -56,115 +56,76 @@ func (s *SubscriptionService) GetUserSubscription(userId string, info common.Pag
|
||||
return subscriptions, total, err
|
||||
}
|
||||
|
||||
// GetUserSubscriptionHistory 获取用户历史订阅过的频道ID列表
|
||||
func (s *SubscriptionService) GetUserSubscriptionHistory(userId string) ([]string, error) {
|
||||
var channelIds []string
|
||||
err := global.DB.Model(&radio.RadioSubscription{}).
|
||||
Where("user_id = ?", userId).
|
||||
Pluck("channel_id", &channelIds).Error
|
||||
return channelIds, err
|
||||
}
|
||||
|
||||
// HasEverSubscribed 检查用户是否曾经订阅过该频道(包括已取消的)
|
||||
func (s *SubscriptionService) HasEverSubscribed(userId, channelId string) (bool, error) {
|
||||
var count int64
|
||||
// 使用Unscoped查询包括已软删除的记录
|
||||
err := global.DB.Unscoped().Model(&radio.RadioSubscription{}).
|
||||
Where("user_id = ? AND channel_id = ?", userId, channelId).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// HasSubscription 检查用户当前是否订阅该频道(未取消的)
|
||||
func (s *SubscriptionService) HasSubscription(userId, channelId string) (bool, error) {
|
||||
var count int64
|
||||
err := global.DB.Model(&radio.RadioSubscription{}).
|
||||
Where("user_id = ? AND channel_id = ?", userId, channelId).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CanSubscribe 检查是否可以订阅
|
||||
// 规则:
|
||||
// 1. 如果用户是VIP且未过期,可以订阅任意频道
|
||||
// 2. 如果用户曾经订阅过该频道(取消后再订阅),可以免费订阅
|
||||
// 3. 否则检查当前有效订阅数量是否达到上限(2个)
|
||||
func (s *SubscriptionService) CanSubscribe(userId, channelId string) (bool, string, error) {
|
||||
// 检查是否已经是订阅用户(未取消的订阅)
|
||||
var existing radio.RadioSubscription
|
||||
err := global.DB.Where("user_id = ? AND channel_id = ?", userId, channelId).First(&existing).Error
|
||||
if err == nil {
|
||||
return false, "您已订阅该频道", nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
// 获取radio_user检查VIP状态
|
||||
var radioUser radio.RadioUser
|
||||
err = global.DB.Where("user_id = ?", userId).First(&radioUser).Error
|
||||
if err == nil && radioUser.IsVip == 1 {
|
||||
// 检查VIP是否过期
|
||||
if radioUser.VipExpireAt != nil && *radioUser.VipExpireAt > time.Now().Unix() {
|
||||
return true, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// 检查用户是否曾经订阅过该频道(取消后又订阅的情况)
|
||||
hasEverSubscribed, err := s.HasEverSubscribed(userId, channelId)
|
||||
// UnlockChannel 解锁频道
|
||||
func (s *SubscriptionService) UnlockChannel(userId string, req request.UnlockChannel) (resp *jsapi.PrepayWithRequestPaymentResponse, no string, err error) {
|
||||
//1.查询频道
|
||||
var channel radio.RadioChannel
|
||||
err = global.DB.Where("id = ?", req.ChannelId).First(&channel).Error
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
return nil, "", err
|
||||
}
|
||||
if hasEverSubscribed {
|
||||
// 曾今订阅过,可以免费再次订阅
|
||||
return true, "", nil
|
||||
//2.创建一个订单 根据eventType 创建不同的订单
|
||||
var price int
|
||||
var orderName string
|
||||
switch req.EventType {
|
||||
case "1":
|
||||
price = channel.MonthlyPrice //包月
|
||||
orderName = channel.Name + " - 月度订阅"
|
||||
case "2":
|
||||
price = channel.QuarterlyPrice //包季
|
||||
orderName = channel.Name + " - 季度订阅"
|
||||
case "3":
|
||||
price = channel.AnnualPrice //包年
|
||||
orderName = channel.Name + " - 年度订阅"
|
||||
default:
|
||||
return nil, "", errors.New("无效的订阅类型")
|
||||
}
|
||||
|
||||
// 非VIP用户,检查当前有效订阅数量(排除已取消的)
|
||||
var count int64
|
||||
err = global.DB.Model(&radio.RadioSubscription{}).Where("user_id = ?", userId).Count(&count).Error
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
if count >= MaxFreeSubscription {
|
||||
return false, "免费订阅数量已达上限(2个),请开通VIP或订阅付费频道", nil
|
||||
}
|
||||
|
||||
return true, "", nil
|
||||
}
|
||||
|
||||
// Subscribe 订阅频道
|
||||
func (s *SubscriptionService) Subscribe(userId, channelId string, subType int) error {
|
||||
subscription := radio.RadioSubscription{
|
||||
order := radio.Order{
|
||||
UserId: userId,
|
||||
ChannelId: channelId,
|
||||
OutTradeNo: uniqueid.GenOrderNo(),
|
||||
ChannelId: req.ChannelId,
|
||||
SubscriptionType: req.EventType,
|
||||
Amount: price,
|
||||
Name: orderName,
|
||||
}
|
||||
return global.DB.Create(&subscription).Error
|
||||
}
|
||||
|
||||
// Unsubscribe 退订频道(逻辑删除,更新删除时间表示已取消)
|
||||
func (s *SubscriptionService) Unsubscribe(userId, channelId string) error {
|
||||
// 软删除:将DeletedAt设置为当前时间,表示已取消订阅
|
||||
// 这样用户可以再次免费订阅该频道
|
||||
return global.DB.Model(&radio.RadioSubscription{}).
|
||||
Where("user_id = ? AND channel_id = ?", userId, channelId).
|
||||
Update("deleted_at", time.Now()).Error
|
||||
}
|
||||
|
||||
// GetVipStatus 获取VIP状态
|
||||
func (s *SubscriptionService) GetVipStatus(userId string) (bool, int64, error) {
|
||||
var radioUser radio.RadioUser
|
||||
err := global.DB.Where("user_id = ?", userId).First(&radioUser).Error
|
||||
err = global.DB.Create(&order).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, 0, nil
|
||||
return nil, "", err
|
||||
}
|
||||
return false, 0, err
|
||||
//4.调用微信api 拉起支付
|
||||
var user radio.RadioUser
|
||||
err = global.DB.Where("user_id = ?", userId).First(&user).Error
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
payClient, err := wechat.GetWxPayClient()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if radioUser.IsVip == 1 && radioUser.VipExpireAt != nil && *radioUser.VipExpireAt > time.Now().Unix() {
|
||||
return true, *radioUser.VipExpireAt, nil
|
||||
svc := jsapi.JsapiApiService{Client: payClient}
|
||||
result, _, err := svc.PrepayWithRequestPayment(context.Background(),
|
||||
jsapi.PrepayRequest{
|
||||
Appid: core.String(global.Config.MiniProgram.AppId),
|
||||
Mchid: core.String(global.Config.WechatPay.MchId),
|
||||
Description: core.String(order.Name),
|
||||
OutTradeNo: core.String(order.OutTradeNo),
|
||||
//TimeExpire: core.Time(time.Now()), //选填
|
||||
//Attach: core.String("自定义数据说明"), //选填
|
||||
NotifyUrl: core.String(global.Config.WechatPay.NotifyUrl),
|
||||
//GoodsTag: core.String("WXG"), //选填
|
||||
//SupportFapiao: core.Bool(false), //选填
|
||||
Amount: &jsapi.Amount{
|
||||
Currency: core.String("CNY"),
|
||||
Total: core.Int64(int64(price)),
|
||||
},
|
||||
Payer: &jsapi.Payer{
|
||||
Openid: core.String(user.OpenId),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return false, 0, nil
|
||||
return result, order.OutTradeNo, nil
|
||||
}
|
||||
|
||||
@@ -46,3 +46,11 @@ func GetZeroTime() time.Time {
|
||||
zeroTime := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local)
|
||||
return zeroTime
|
||||
}
|
||||
|
||||
// GetMaxTime 获取当天最大时间
|
||||
func GetMaxTime() time.Time {
|
||||
now := time.Now()
|
||||
// 2. 使用当天的年月日,将时分秒纳秒设为0,并保留原时区(Location)
|
||||
maxTime := time.Date(now.Year(), now.Month(), now.Day(), 23, 59, 59, 999999999, time.Local)
|
||||
return maxTime
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user