parent
09de05548f
commit
45b54b3455
@ -1,42 +0,0 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
alipay "github.com/smartwalle/alipay/v3"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Alipay 支付宝当面付支付处理
|
||||
type Alipay struct {
|
||||
Client *alipay.Client
|
||||
}
|
||||
|
||||
// Create 创建订单
|
||||
func (pay *Alipay) Create(order *model.Order, pack *serializer.PackProduct, group *serializer.GroupProducts, user *model.User) (*OrderCreateRes, error) {
|
||||
gateway, _ := url.Parse("/api/v3/callback/alipay")
|
||||
var p = alipay.TradePreCreate{
|
||||
Trade: alipay.Trade{
|
||||
NotifyURL: model.GetSiteURL().ResolveReference(gateway).String(),
|
||||
Subject: order.Name,
|
||||
OutTradeNo: order.OrderNo,
|
||||
TotalAmount: fmt.Sprintf("%.2f", float64(order.Price*order.Num)/100),
|
||||
},
|
||||
}
|
||||
|
||||
if _, err := order.Create(); err != nil {
|
||||
return nil, ErrInsertOrder.WithError(err)
|
||||
}
|
||||
|
||||
res, err := pay.Client.TradePreCreate(p)
|
||||
if err != nil {
|
||||
return nil, ErrIssueOrder.WithError(err)
|
||||
}
|
||||
|
||||
return &OrderCreateRes{
|
||||
Payment: true,
|
||||
QRCode: res.Content.QRCode,
|
||||
ID: order.OrderNo,
|
||||
}, nil
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/qingwg/payjs"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnknownPaymentMethod 未知支付方式
|
||||
ErrUnknownPaymentMethod = serializer.NewError(serializer.CodeNotFound, "未知支付方式", nil)
|
||||
// ErrUnsupportedPaymentMethod 未知支付方式
|
||||
ErrUnsupportedPaymentMethod = serializer.NewError(serializer.CodeNotFound, "此订单不支持此支付方式", nil)
|
||||
// ErrInsertOrder 无法插入订单记录
|
||||
ErrInsertOrder = serializer.NewError(serializer.CodeDBError, "无法插入订单记录", nil)
|
||||
// ErrScoreNotEnough 积分不足
|
||||
ErrScoreNotEnough = serializer.NewError(serializer.CodeNoPermissionErr, "积分不足", nil)
|
||||
// ErrCreateStoragePack 无法创建容量包
|
||||
ErrCreateStoragePack = serializer.NewError(serializer.CodeNoPermissionErr, "无法创建容量包", nil)
|
||||
// ErrGroupConflict 用户组冲突
|
||||
ErrGroupConflict = serializer.NewError(serializer.CodeNoPermissionErr, "当前用户组仍未过期,请前往个人设置手动解约后继续", nil)
|
||||
// ErrGroupInvalid 用户组冲突
|
||||
ErrGroupInvalid = serializer.NewError(serializer.CodeNoPermissionErr, "用户组不可用", nil)
|
||||
// ErrUpgradeGroup 用户组冲突
|
||||
ErrUpgradeGroup = serializer.NewError(serializer.CodeDBError, "无法升级用户组", nil)
|
||||
// ErrUInitPayment 无法初始化支付实例
|
||||
ErrUInitPayment = serializer.NewError(serializer.CodeInternalSetting, "无法初始化支付实例", nil)
|
||||
// ErrIssueOrder 订单接口请求失败
|
||||
ErrIssueOrder = serializer.NewError(serializer.CodeInternalSetting, "无法创建订单", nil)
|
||||
// ErrOrderNotFound 订单不存在
|
||||
ErrOrderNotFound = serializer.NewError(serializer.CodeNotFound, "订单不存在", nil)
|
||||
)
|
||||
|
||||
// Pay 支付处理接口
|
||||
type Pay interface {
|
||||
Create(order *model.Order, pack *serializer.PackProduct, group *serializer.GroupProducts, user *model.User) (*OrderCreateRes, error)
|
||||
}
|
||||
|
||||
// OrderCreateRes 订单创建结果
|
||||
type OrderCreateRes struct {
|
||||
Payment bool `json:"payment"` // 是否需要支付
|
||||
ID string `json:"id,omitempty"` // 订单号
|
||||
QRCode string `json:"qr_code,omitempty"` // 支付二维码指向的地址
|
||||
}
|
||||
|
||||
// NewPaymentInstance 获取新的支付实例
|
||||
func NewPaymentInstance(method string) (Pay, error) {
|
||||
switch method {
|
||||
case "score":
|
||||
return &ScorePayment{}, nil
|
||||
case "alipay":
|
||||
options := model.GetSettingByNames("alipay_enabled", "appid", "appkey", "shopid")
|
||||
if options["alipay_enabled"] != "1" {
|
||||
return nil, ErrUnknownPaymentMethod
|
||||
}
|
||||
|
||||
// 初始化支付宝客户端
|
||||
var client, err = alipay.New(options["appid"], options["appkey"], true)
|
||||
if err != nil {
|
||||
return nil, ErrUInitPayment.WithError(err)
|
||||
}
|
||||
|
||||
// 加载支付宝公钥
|
||||
err = client.LoadAliPayPublicKey(options["shopid"])
|
||||
if err != nil {
|
||||
return nil, ErrUInitPayment.WithError(err)
|
||||
}
|
||||
|
||||
return &Alipay{Client: client}, nil
|
||||
case "payjs":
|
||||
options := model.GetSettingByNames("payjs_enabled", "payjs_secret", "payjs_id")
|
||||
if options["payjs_enabled"] != "1" {
|
||||
return nil, ErrUnknownPaymentMethod
|
||||
}
|
||||
|
||||
callback, _ := url.Parse("/api/v3/callback/payjs")
|
||||
payjsConfig := &payjs.Config{
|
||||
Key: options["payjs_secret"],
|
||||
MchID: options["payjs_id"],
|
||||
NotifyUrl: model.GetSiteURL().ResolveReference(callback).String(),
|
||||
}
|
||||
|
||||
return &PayJSClient{Client: payjs.New(payjsConfig)}, nil
|
||||
|
||||
default:
|
||||
return nil, ErrUnknownPaymentMethod
|
||||
}
|
||||
}
|
||||
|
||||
// NewOrder 创建新订单
|
||||
func NewOrder(pack *serializer.PackProduct, group *serializer.GroupProducts, num int, method string, user *model.User) (*OrderCreateRes, error) {
|
||||
// 获取支付实例
|
||||
pay, err := NewPaymentInstance(method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
orderType int
|
||||
productID int64
|
||||
title string
|
||||
price int
|
||||
)
|
||||
if pack != nil {
|
||||
orderType = model.PackOrderType
|
||||
productID = pack.ID
|
||||
title = pack.Name
|
||||
price = pack.Price
|
||||
} else if group != nil {
|
||||
orderType = model.GroupOrderType
|
||||
productID = group.ID
|
||||
title = group.Name
|
||||
price = group.Price
|
||||
} else {
|
||||
orderType = model.ScoreOrderType
|
||||
productID = 0
|
||||
title = fmt.Sprintf("%d 积分", num)
|
||||
price = model.GetIntSetting("score_price", 1)
|
||||
}
|
||||
|
||||
// 创建订单记录
|
||||
order := &model.Order{
|
||||
UserID: user.ID,
|
||||
OrderNo: orderID(),
|
||||
Type: orderType,
|
||||
Method: method,
|
||||
ProductID: productID,
|
||||
Num: num,
|
||||
Name: fmt.Sprintf("%s - %s", model.GetSettingByName("siteName"), title),
|
||||
Price: price,
|
||||
Status: model.OrderUnpaid,
|
||||
}
|
||||
|
||||
return pay.Create(order, pack, group, user)
|
||||
}
|
||||
|
||||
func orderID() string {
|
||||
return fmt.Sprintf("%s%d",
|
||||
time.Now().Format("20060102150405"),
|
||||
100000+rand.Intn(900000),
|
||||
)
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/qingwg/payjs"
|
||||
)
|
||||
|
||||
// PayJSClient PayJS支付处理
|
||||
type PayJSClient struct {
|
||||
Client *payjs.PayJS
|
||||
}
|
||||
|
||||
// Create 创建订单
|
||||
func (pay *PayJSClient) Create(order *model.Order, pack *serializer.PackProduct, group *serializer.GroupProducts, user *model.User) (*OrderCreateRes, error) {
|
||||
if _, err := order.Create(); err != nil {
|
||||
return nil, ErrInsertOrder.WithError(err)
|
||||
}
|
||||
|
||||
PayNative := pay.Client.GetNative()
|
||||
res, err := PayNative.Create(int64(order.Price*order.Num), order.Name, order.OrderNo, "", "")
|
||||
if err != nil {
|
||||
return nil, ErrIssueOrder.WithError(err)
|
||||
}
|
||||
|
||||
return &OrderCreateRes{
|
||||
Payment: true,
|
||||
QRCode: res.CodeUrl,
|
||||
ID: order.OrderNo,
|
||||
}, nil
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GivePack 创建容量包
|
||||
func GivePack(user *model.User, packInfo *serializer.PackProduct, num int) error {
|
||||
timeNow := time.Now()
|
||||
expires := timeNow.Add(time.Duration(packInfo.Time*int64(num)) * time.Second)
|
||||
pack := model.StoragePack{
|
||||
Name: packInfo.Name,
|
||||
UserID: user.ID,
|
||||
ActiveTime: &timeNow,
|
||||
ExpiredTime: &expires,
|
||||
Size: packInfo.Size,
|
||||
}
|
||||
if _, err := pack.Create(); err != nil {
|
||||
return ErrCreateStoragePack.WithError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkGroupUpgrade(user *model.User, groupInfo *serializer.GroupProducts) error {
|
||||
// 检查用户是否已有未过期用户
|
||||
if user.PreviousGroupID != 0 {
|
||||
return ErrGroupConflict
|
||||
}
|
||||
|
||||
// 用户组不能相同
|
||||
if user.GroupID == groupInfo.GroupID {
|
||||
return ErrGroupInvalid
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GiveGroup 升级用户组
|
||||
func GiveGroup(user *model.User, groupInfo *serializer.GroupProducts, num int) error {
|
||||
if err := checkGroupUpgrade(user, groupInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeNow := time.Now()
|
||||
expires := timeNow.Add(time.Duration(groupInfo.Time*int64(num)) * time.Second)
|
||||
|
||||
if err := user.UpgradeGroup(groupInfo.GroupID, &expires); err != nil {
|
||||
return ErrUpgradeGroup.WithError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GiveScore 积分充值
|
||||
func GiveScore(user *model.User, num int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GiveProduct “发货”
|
||||
func GiveProduct(user *model.User, pack *serializer.PackProduct, group *serializer.GroupProducts, num int) error {
|
||||
if pack != nil {
|
||||
return GivePack(user, pack, num)
|
||||
} else if group != nil {
|
||||
return GiveGroup(user, group, num)
|
||||
} else {
|
||||
return GiveScore(user, num)
|
||||
}
|
||||
}
|
||||
|
||||
// OrderPaid 订单已支付处理
|
||||
func OrderPaid(orderNo string) error {
|
||||
order, err := model.GetOrderByNo(orderNo)
|
||||
if err != nil {
|
||||
return ErrOrderNotFound.WithError(err)
|
||||
}
|
||||
|
||||
// 更新订单状态为 已支付
|
||||
order.UpdateStatus(model.OrderPaid)
|
||||
|
||||
user, err := model.GetActiveUserByID(order.UserID)
|
||||
if err != nil {
|
||||
return errors.New("用户不存在")
|
||||
}
|
||||
|
||||
// 查询商品
|
||||
options := model.GetSettingByNames("pack_data", "group_sell_data")
|
||||
|
||||
var (
|
||||
packs []serializer.PackProduct
|
||||
groups []serializer.GroupProducts
|
||||
)
|
||||
if err := json.Unmarshal([]byte(options["pack_data"]), &packs); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(options["group_sell_data"]), &groups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 查找要购买的商品
|
||||
var (
|
||||
pack *serializer.PackProduct
|
||||
group *serializer.GroupProducts
|
||||
)
|
||||
if order.Type == model.GroupOrderType {
|
||||
for _, v := range groups {
|
||||
if v.ID == order.ProductID {
|
||||
group = &v
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if order.Type == model.PackOrderType {
|
||||
for _, v := range packs {
|
||||
if v.ID == order.ProductID {
|
||||
pack = &v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "发货"
|
||||
return GiveProduct(&user, pack, group, order.Num)
|
||||
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
package payment
|
||||
|
||||
import (
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
)
|
||||
|
||||
// ScorePayment 积分支付处理
|
||||
type ScorePayment struct {
|
||||
}
|
||||
|
||||
// Create 创建新订单
|
||||
func (pay *ScorePayment) Create(order *model.Order, pack *serializer.PackProduct, group *serializer.GroupProducts, user *model.User) (*OrderCreateRes, error) {
|
||||
if pack != nil {
|
||||
order.Price = pack.Score
|
||||
} else {
|
||||
order.Price = group.Score
|
||||
}
|
||||
|
||||
// 检查此订单是否可用积分支付
|
||||
if order.Price == 0 {
|
||||
return nil, ErrUnsupportedPaymentMethod
|
||||
}
|
||||
|
||||
// 创建订单记录
|
||||
order.Status = model.OrderPaid
|
||||
if _, err := order.Create(); err != nil {
|
||||
return nil, ErrInsertOrder.WithError(err)
|
||||
}
|
||||
|
||||
return &OrderCreateRes{
|
||||
Payment: false,
|
||||
}, nil
|
||||
}
|
@ -1,211 +0,0 @@
|
||||
package qq
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/request"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/gofrs/uuid"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// LoginPage 登陆页面描述
|
||||
type LoginPage struct {
|
||||
URL string
|
||||
SecretKey string
|
||||
}
|
||||
|
||||
// UserCredentials 登陆成功后的凭证
|
||||
type UserCredentials struct {
|
||||
OpenID string
|
||||
AccessToken string
|
||||
}
|
||||
|
||||
// UserInfo 用户信息
|
||||
type UserInfo struct {
|
||||
Nick string
|
||||
Avatar string
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrNotEnabled 未开启登录功能
|
||||
ErrNotEnabled = serializer.NewError(serializer.CodeNoPermissionErr, "QQ登录功能未开启", nil)
|
||||
// ErrObtainAccessToken 无法获取AccessToken
|
||||
ErrObtainAccessToken = serializer.NewError(serializer.CodeParamErr, "无法获取AccessToken", nil)
|
||||
// ErrObtainOpenID 无法获取OpenID
|
||||
ErrObtainOpenID = serializer.NewError(serializer.CodeParamErr, "无法获取OpenID", nil)
|
||||
//ErrDecodeResponse 无法解析服务端响应
|
||||
ErrDecodeResponse = serializer.NewError(serializer.CodeInternalSetting, "无法解析服务端响应", nil)
|
||||
)
|
||||
|
||||
// NewLoginRequest 新建登录会话
|
||||
func NewLoginRequest() (*LoginPage, error) {
|
||||
// 获取相关设定
|
||||
options := model.GetSettingByNames("qq_login", "qq_login_id")
|
||||
if options["qq_login"] == "0" {
|
||||
return nil, ErrNotEnabled
|
||||
}
|
||||
|
||||
// 生成唯一ID
|
||||
u2, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret := fmt.Sprintf("%x", md5.Sum(u2.Bytes()))
|
||||
|
||||
// 生成登录地址
|
||||
loginURL, _ := url.Parse("https://graph.qq.com/oauth2.0/authorize?response_type=code")
|
||||
queries := loginURL.Query()
|
||||
queries.Add("client_id", options["qq_login_id"])
|
||||
queries.Add("redirect_uri", getCallbackURL())
|
||||
queries.Add("state", secret)
|
||||
loginURL.RawQuery = queries.Encode()
|
||||
|
||||
return &LoginPage{
|
||||
URL: loginURL.String(),
|
||||
SecretKey: secret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getCallbackURL() string {
|
||||
//return "https://drive.aoaoao.me/Callback/QQ"
|
||||
// 生成回调地址
|
||||
gateway, _ := url.Parse("/#/login/qq")
|
||||
callback := model.GetSiteURL().ResolveReference(gateway).String()
|
||||
|
||||
return callback
|
||||
}
|
||||
|
||||
func getAccessTokenURL(code string) string {
|
||||
// 获取相关设定
|
||||
options := model.GetSettingByNames("qq_login_id", "qq_login_key")
|
||||
|
||||
api, _ := url.Parse("https://graph.qq.com/oauth2.0/token?grant_type=authorization_code")
|
||||
queries := api.Query()
|
||||
queries.Add("client_id", options["qq_login_id"])
|
||||
queries.Add("redirect_uri", getCallbackURL())
|
||||
queries.Add("client_secret", options["qq_login_key"])
|
||||
queries.Add("code", code)
|
||||
api.RawQuery = queries.Encode()
|
||||
|
||||
return api.String()
|
||||
}
|
||||
|
||||
func getUserInfoURL(openid, ak string) string {
|
||||
// 获取相关设定
|
||||
options := model.GetSettingByNames("qq_login_id", "qq_login_key")
|
||||
|
||||
api, _ := url.Parse("https://graph.qq.com/user/get_user_info")
|
||||
queries := api.Query()
|
||||
queries.Add("oauth_consumer_key", options["qq_login_id"])
|
||||
queries.Add("openid", openid)
|
||||
queries.Add("access_token", ak)
|
||||
api.RawQuery = queries.Encode()
|
||||
|
||||
return api.String()
|
||||
}
|
||||
|
||||
func getResponse(body string) (map[string]interface{}, error) {
|
||||
var res map[string]interface{}
|
||||
|
||||
if !strings.Contains(body, "callback") {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
body = strings.TrimPrefix(body, "callback(")
|
||||
body = strings.TrimSuffix(body, ");\n")
|
||||
|
||||
err := json.Unmarshal([]byte(body), &res)
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Callback 处理回调,返回openid和access key
|
||||
func Callback(code string) (*UserCredentials, error) {
|
||||
// 获取相关设定
|
||||
options := model.GetSettingByNames("qq_login")
|
||||
if options["qq_login"] == "0" {
|
||||
return nil, ErrNotEnabled
|
||||
}
|
||||
|
||||
api := getAccessTokenURL(code)
|
||||
|
||||
// 获取AccessToken
|
||||
client := request.HTTPClient{}
|
||||
res := client.Request("GET", api, nil)
|
||||
resp, err := res.GetResponse()
|
||||
if err != nil {
|
||||
return nil, ErrObtainAccessToken.WithError(err)
|
||||
}
|
||||
|
||||
// 如果服务端返回错误
|
||||
errResp, err := getResponse(resp)
|
||||
if msg, ok := errResp["error_description"]; err == nil && ok {
|
||||
return nil, ErrObtainAccessToken.WithError(errors.New(msg.(string)))
|
||||
}
|
||||
|
||||
// 获取AccessToken
|
||||
vals, err := url.ParseQuery(resp)
|
||||
if err != nil {
|
||||
return nil, ErrDecodeResponse.WithError(err)
|
||||
}
|
||||
accessToken := vals.Get("access_token")
|
||||
|
||||
// 用 AccessToken 换取OpenID
|
||||
res = client.Request("GET", "https://graph.qq.com/oauth2.0/me?access_token="+accessToken, nil)
|
||||
resp, err = res.GetResponse()
|
||||
if err != nil {
|
||||
return nil, ErrObtainOpenID.WithError(err)
|
||||
}
|
||||
|
||||
// 解析服务端响应
|
||||
errResp, err = getResponse(resp)
|
||||
if msg, ok := errResp["error_description"]; err == nil && ok {
|
||||
return nil, ErrObtainOpenID.WithError(errors.New(msg.(string)))
|
||||
}
|
||||
|
||||
if openid, ok := errResp["openid"]; ok {
|
||||
return &UserCredentials{
|
||||
OpenID: openid.(string),
|
||||
AccessToken: accessToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, ErrDecodeResponse
|
||||
}
|
||||
|
||||
// GetUserInfo 使用凭证获取用户信息
|
||||
func GetUserInfo(credential *UserCredentials) (*UserInfo, error) {
|
||||
api := getUserInfoURL(credential.OpenID, credential.AccessToken)
|
||||
|
||||
// 获取用户信息
|
||||
client := request.HTTPClient{}
|
||||
res := client.Request("GET", api, nil)
|
||||
resp, err := res.GetResponse()
|
||||
if err != nil {
|
||||
return nil, ErrObtainAccessToken.WithError(err)
|
||||
}
|
||||
|
||||
var resSerialized map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(resp), &resSerialized); err != nil {
|
||||
return nil, ErrDecodeResponse.WithError(err)
|
||||
}
|
||||
|
||||
// 如果服务端返回错误
|
||||
if msg, ok := resSerialized["msg"]; ok && msg.(string) != "" {
|
||||
return nil, ErrObtainAccessToken.WithError(errors.New(msg.(string)))
|
||||
}
|
||||
|
||||
if avatar, ok := resSerialized["figureurl_qq_2"]; ok {
|
||||
return &UserInfo{
|
||||
Nick: resSerialized["nickname"].(string),
|
||||
Avatar: avatar.(string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, ErrDecodeResponse
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package serializer
|
||||
|
||||
import (
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/hashid"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
)
|
||||
|
||||
type quota struct {
|
||||
Base uint64 `json:"base"`
|
||||
Pack uint64 `json:"pack"`
|
||||
Used uint64 `json:"used"`
|
||||
Total uint64 `json:"total"`
|
||||
Packs []storagePacks `json:"packs"`
|
||||
}
|
||||
|
||||
type storagePacks struct {
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
ActivateDate string `json:"activate_date"`
|
||||
Expiration int `json:"expiration"`
|
||||
ExpirationDate string `json:"expiration_date"`
|
||||
}
|
||||
|
||||
// MountedFolders 已挂载的目录
|
||||
type MountedFolders struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PolicyName string `json:"policy_name"`
|
||||
}
|
||||
|
||||
type policyOptions struct {
|
||||
Name string `json:"name"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// BuildPolicySettingRes 构建存储策略选项选择
|
||||
func BuildPolicySettingRes(policies []model.Policy, current *model.Policy) Response {
|
||||
options := make([]policyOptions, 0, len(policies))
|
||||
for _, policy := range policies {
|
||||
options = append(options, policyOptions{
|
||||
Name: policy.Name,
|
||||
ID: hashid.HashID(policy.ID, hashid.PolicyID),
|
||||
})
|
||||
}
|
||||
|
||||
return Response{
|
||||
Data: map[string]interface{}{
|
||||
"options": options,
|
||||
"current": policyOptions{
|
||||
Name: current.Name,
|
||||
ID: hashid.HashID(current.ID, hashid.PolicyID),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// BuildUserQuotaResponse 序列化用户存储配额概况响应
|
||||
func BuildUserQuotaResponse(user *model.User, packs []model.StoragePack) Response {
|
||||
packSize := user.GetAvailablePackSize()
|
||||
res := quota{
|
||||
Base: user.Group.MaxStorage,
|
||||
Pack: packSize,
|
||||
Used: user.Storage,
|
||||
Total: packSize + user.Group.MaxStorage,
|
||||
Packs: make([]storagePacks, 0, len(packs)),
|
||||
}
|
||||
for _, pack := range packs {
|
||||
res.Packs = append(res.Packs, storagePacks{
|
||||
Name: pack.Name,
|
||||
Size: pack.Size,
|
||||
ActivateDate: pack.ActiveTime.Format("2006-01-02 15:04:05"),
|
||||
Expiration: int(pack.ExpiredTime.Sub(*pack.ActiveTime).Seconds()),
|
||||
ExpirationDate: pack.ExpiredTime.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
}
|
||||
|
||||
return Response{
|
||||
Data: res,
|
||||
}
|
||||
}
|
||||
|
||||
// PackProduct 容量包商品
|
||||
type PackProduct struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Size uint64 `json:"size"`
|
||||
Time int64 `json:"time"`
|
||||
Price int `json:"price"`
|
||||
Score int `json:"score"`
|
||||
}
|
||||
|
||||
// GroupProducts 用户组商品
|
||||
type GroupProducts struct {
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
GroupID uint `json:"group_id"`
|
||||
Time int64 `json:"time"`
|
||||
Price int `json:"price"`
|
||||
Score int `json:"score"`
|
||||
Des []string `json:"des"`
|
||||
Highlight bool `json:"highlight"`
|
||||
}
|
||||
|
||||
// BuildProductResponse 构建增值服务商品响应
|
||||
func BuildProductResponse(groups []GroupProducts, packs []PackProduct, alipay, payjs bool, scorePrice int) Response {
|
||||
// 隐藏响应中的用户组ID
|
||||
for i := 0; i < len(groups); i++ {
|
||||
groups[i].GroupID = 0
|
||||
}
|
||||
return Response{
|
||||
Data: map[string]interface{}{
|
||||
"packs": packs,
|
||||
"groups": groups,
|
||||
"alipay": alipay,
|
||||
"payjs": payjs,
|
||||
"score_price": scorePrice,
|
||||
},
|
||||
}
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/HFO4/cloudreve/pkg/payment"
|
||||
"github.com/HFO4/cloudreve/pkg/util"
|
||||
"github.com/HFO4/cloudreve/service/vas"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/qingwg/payjs/notify"
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
)
|
||||
|
||||
// GetQuota 获取容量配额信息
|
||||
func GetQuota(c *gin.Context) {
|
||||
var service vas.GeneralVASService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.Quota(c, CurrentUser(c))
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetProduct 获取商品信息
|
||||
func GetProduct(c *gin.Context) {
|
||||
var service vas.GeneralVASService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.Products(c, CurrentUser(c))
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// NewOrder 新建支付订单
|
||||
func NewOrder(c *gin.Context) {
|
||||
var service vas.CreateOrderService
|
||||
if err := c.ShouldBindJSON(&service); err == nil {
|
||||
res := service.Create(c, CurrentUser(c))
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// OrderStatus 查询订单状态
|
||||
func OrderStatus(c *gin.Context) {
|
||||
var service vas.OrderService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.Status(c, CurrentUser(c))
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// GetRedeemInfo 获取兑换码信息
|
||||
func GetRedeemInfo(c *gin.Context) {
|
||||
var service vas.RedeemService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.Query(c)
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// DoRedeem 获取兑换码信息
|
||||
func DoRedeem(c *gin.Context) {
|
||||
var service vas.RedeemService
|
||||
if err := c.ShouldBindUri(&service); err == nil {
|
||||
res := service.Redeem(c, CurrentUser(c))
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
||||
|
||||
// AlipayCallback 支付宝回调
|
||||
func AlipayCallback(c *gin.Context) {
|
||||
pay, err := payment.NewPaymentInstance("alipay")
|
||||
if err != nil {
|
||||
util.Log().Debug("[支付宝回调] 无法创建支付宝客户端, %s", err)
|
||||
c.Status(400)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := pay.(*payment.Alipay).Client.GetTradeNotification(c.Request)
|
||||
if err != nil {
|
||||
util.Log().Debug("[支付宝回调] 回调验证失败, %s", err)
|
||||
c.Status(403)
|
||||
return
|
||||
}
|
||||
|
||||
if res != nil && res.TradeStatus == "TRADE_SUCCESS" {
|
||||
// 支付成功
|
||||
if err := payment.OrderPaid(res.OutTradeNo); err != nil {
|
||||
util.Log().Debug("[支付宝回调] 支付处理失败, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 确认收到通知消息
|
||||
alipay.AckNotification(c.Writer)
|
||||
}
|
||||
|
||||
// PayJSCallback PayJS回调
|
||||
func PayJSCallback(c *gin.Context) {
|
||||
pay, err := payment.NewPaymentInstance("payjs")
|
||||
if err != nil {
|
||||
util.Log().Debug("[PayJS回调] 无法创建支付宝客户端, %s", err)
|
||||
c.Status(400)
|
||||
return
|
||||
}
|
||||
|
||||
payNotify := pay.(*payment.PayJSClient).Client.GetNotify(c.Request, c.Writer)
|
||||
|
||||
//设置接收消息的处理方法
|
||||
payNotify.SetMessageHandler(func(msg notify.Message) {
|
||||
if err := payment.OrderPaid(msg.OutTradeNo); err != nil {
|
||||
util.Log().Debug("[PayJS回调] 支付处理失败, %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
//处理消息接收以及回复
|
||||
err = payNotify.Serve()
|
||||
if err != nil {
|
||||
util.Log().Debug("[PayJS回调] 回调处理失败, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
//发送回复的消息
|
||||
payNotify.SendResponseMsg()
|
||||
|
||||
}
|
||||
|
||||
// QQCallback QQ互联回调
|
||||
func QQCallback(c *gin.Context) {
|
||||
var service vas.QQCallbackService
|
||||
if err := c.ShouldBindJSON(&service); err == nil {
|
||||
res := service.Callback(c, CurrentUser(c))
|
||||
c.JSON(200, res)
|
||||
} else {
|
||||
c.JSON(200, ErrorResponse(err))
|
||||
}
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/gofrs/uuid"
|
||||
)
|
||||
|
||||
// GenerateRedeemsService 兑换码生成服务
|
||||
type GenerateRedeemsService struct {
|
||||
Num int `json:"num" binding:"required,min=1,max=100"`
|
||||
ID int64 `json:"id"`
|
||||
Time int `json:"time" binding:"required,min=1"`
|
||||
Type int `json:"type" binding:"min=0,max=2"`
|
||||
}
|
||||
|
||||
// SingleIDService 单ID服务
|
||||
type SingleIDService struct {
|
||||
ID uint `uri:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteRedeem 删除兑换码
|
||||
func (service *SingleIDService) DeleteRedeem() serializer.Response {
|
||||
if err := model.DB.Where("id = ?", service.ID).Delete(&model.Redeem{}).Error; err != nil {
|
||||
return serializer.DBErr("无法删除兑换码", err)
|
||||
}
|
||||
|
||||
return serializer.Response{}
|
||||
}
|
||||
|
||||
// Generate 生成兑换码
|
||||
func (service *GenerateRedeemsService) Generate() serializer.Response {
|
||||
res := make([]string, service.Num)
|
||||
redeem := model.Redeem{}
|
||||
|
||||
// 开始事务
|
||||
tx := model.DB.Begin()
|
||||
if err := tx.Error; err != nil {
|
||||
return serializer.DBErr("无法开启事务", err)
|
||||
}
|
||||
|
||||
// 创建每个兑换码
|
||||
for i := 0; i < service.Num; i++ {
|
||||
redeem.Model.ID = 0
|
||||
redeem.Num = service.Time
|
||||
redeem.Type = service.Type
|
||||
redeem.ProductID = service.ID
|
||||
redeem.Used = false
|
||||
|
||||
// 生成唯一兑换码
|
||||
u2, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法生成兑换码", err)
|
||||
}
|
||||
|
||||
redeem.Code = u2.String()
|
||||
if err := tx.Create(&redeem).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return serializer.DBErr("无法创建兑换码记录", err)
|
||||
}
|
||||
|
||||
res[i] = redeem.Code
|
||||
}
|
||||
|
||||
if err := tx.Commit().Error; err != nil {
|
||||
return serializer.DBErr("无法创建兑换码记录", err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res}
|
||||
|
||||
}
|
||||
|
||||
// Redeems 列出激活码
|
||||
func (service *AdminListService) Redeems() serializer.Response {
|
||||
var res []model.Redeem
|
||||
total := 0
|
||||
|
||||
tx := model.DB.Model(&model.Redeem{})
|
||||
if service.OrderBy != "" {
|
||||
tx = tx.Order(service.OrderBy)
|
||||
}
|
||||
|
||||
for k, v := range service.Conditions {
|
||||
tx = tx.Where("? = ?", k, v)
|
||||
}
|
||||
|
||||
// 计算总数用于分页
|
||||
tx.Count(&total)
|
||||
|
||||
// 查询记录
|
||||
tx.Limit(service.PageSize).Offset((service.Page - 1) * service.PageSize).Find(&res)
|
||||
|
||||
return serializer.Response{Data: map[string]interface{}{
|
||||
"total": total,
|
||||
"items": res,
|
||||
}}
|
||||
}
|
@ -1,219 +0,0 @@
|
||||
package vas
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/payment"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CreateOrderService 创建订单服务
|
||||
type CreateOrderService struct {
|
||||
Action string `json:"action" binding:"required,eq=group|eq=pack|eq=score"`
|
||||
Method string `json:"method" binding:"required,eq=alipay|eq=score|eq=payjs"`
|
||||
ID int64 `json:"id" binding:"required"`
|
||||
Num int `json:"num" binding:"required,min=1"`
|
||||
}
|
||||
|
||||
// RedeemService 兑换服务
|
||||
type RedeemService struct {
|
||||
Code string `uri:"code" binding:"required,max=64"`
|
||||
}
|
||||
|
||||
// OrderService 订单查询
|
||||
type OrderService struct {
|
||||
ID string `uri:"id" binding:"required"`
|
||||
}
|
||||
|
||||
// Status 查询订单状态
|
||||
func (service *OrderService) Status(c *gin.Context, user *model.User) serializer.Response {
|
||||
order, _ := model.GetOrderByNo(service.ID)
|
||||
if order == nil || order.UserID != user.ID {
|
||||
return serializer.Err(serializer.CodeNotFound, "订单不存在", nil)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: order.Status}
|
||||
}
|
||||
|
||||
// Redeem 开始兑换
|
||||
func (service *RedeemService) Redeem(c *gin.Context, user *model.User) serializer.Response {
|
||||
redeem, err := model.GetAvailableRedeem(service.Code)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "兑换码无效", err)
|
||||
}
|
||||
|
||||
// 取得当前商品信息
|
||||
packs, groups, err := decodeProductInfo()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法解析商品设置", err)
|
||||
}
|
||||
|
||||
// 查找要购买的商品
|
||||
var (
|
||||
pack *serializer.PackProduct
|
||||
group *serializer.GroupProducts
|
||||
)
|
||||
if redeem.Type == model.GroupOrderType {
|
||||
for _, v := range groups {
|
||||
if v.ID == redeem.ProductID {
|
||||
group = &v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if group == nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "商品已失效", err)
|
||||
}
|
||||
|
||||
} else if redeem.Type == model.PackOrderType {
|
||||
for _, v := range packs {
|
||||
if v.ID == redeem.ProductID {
|
||||
pack = &v
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if pack == nil {
|
||||
return serializer.Err(serializer.CodeNotFound, "商品已失效", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = payment.GiveProduct(user, pack, group, redeem.Num)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, "兑换失败", err)
|
||||
}
|
||||
|
||||
redeem.Use()
|
||||
|
||||
return serializer.Response{}
|
||||
|
||||
}
|
||||
|
||||
// Query 检查兑换码信息
|
||||
func (service *RedeemService) Query(c *gin.Context) serializer.Response {
|
||||
redeem, err := model.GetAvailableRedeem(service.Code)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeDBError, "兑换码无效", err)
|
||||
}
|
||||
|
||||
var (
|
||||
name = "积分"
|
||||
productTime int64
|
||||
)
|
||||
if redeem.Type != model.ScoreOrderType {
|
||||
packs, groups, err := decodeProductInfo()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法解析商品设置", err)
|
||||
}
|
||||
if redeem.Type == model.GroupOrderType {
|
||||
for _, v := range groups {
|
||||
if v.ID == redeem.ProductID {
|
||||
name = v.Name
|
||||
productTime = v.Time
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, v := range packs {
|
||||
if v.ID == redeem.ProductID {
|
||||
name = v.Name
|
||||
productTime = v.Time
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if name == "积分" {
|
||||
return serializer.Err(serializer.CodeNotFound, "商品已失效", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return serializer.Response{
|
||||
Data: struct {
|
||||
Name string `json:"name"`
|
||||
Type int `json:"type"`
|
||||
Num int `json:"num"`
|
||||
Time int64 `json:"time"`
|
||||
}{
|
||||
name, redeem.Type, redeem.Num, productTime,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建新订单
|
||||
func (service *CreateOrderService) Create(c *gin.Context, user *model.User) serializer.Response {
|
||||
// 取得当前商品信息
|
||||
packs, groups, err := decodeProductInfo()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法解析商品设置", err)
|
||||
}
|
||||
|
||||
// 查找要购买的商品
|
||||
var (
|
||||
pack *serializer.PackProduct
|
||||
group *serializer.GroupProducts
|
||||
)
|
||||
if service.Action == "group" {
|
||||
for _, v := range groups {
|
||||
if v.ID == service.ID {
|
||||
group = &v
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if service.Action == "pack" {
|
||||
for _, v := range packs {
|
||||
if v.ID == service.ID {
|
||||
pack = &v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 购买积分
|
||||
if pack == nil && group == nil {
|
||||
if service.Method == "score" {
|
||||
return serializer.Err(serializer.CodeNotFound, "不支持此支付方式", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
res, err := payment.NewOrder(pack, group, service.Num, service.Method, user)
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeNotSet, err.Error(), err)
|
||||
}
|
||||
|
||||
return serializer.Response{Data: res}
|
||||
|
||||
}
|
||||
|
||||
// Products 获取商品信息
|
||||
func (service *GeneralVASService) Products(c *gin.Context, user *model.User) serializer.Response {
|
||||
options := model.GetSettingByNames("alipay_enabled", "payjs_enabled", "score_price")
|
||||
scorePrice := model.GetIntSetting("score_price", 0)
|
||||
packs, groups, err := decodeProductInfo()
|
||||
if err != nil {
|
||||
return serializer.Err(serializer.CodeInternalSetting, "无法解析商品设置", err)
|
||||
}
|
||||
|
||||
return serializer.BuildProductResponse(groups, packs, options["alipay_enabled"] == "1", options["payjs_enabled"] == "1", scorePrice)
|
||||
}
|
||||
|
||||
func decodeProductInfo() ([]serializer.PackProduct, []serializer.GroupProducts, error) {
|
||||
options := model.GetSettingByNames("pack_data", "group_sell_data", "alipay_enabled", "payjs_enabled")
|
||||
|
||||
var (
|
||||
packs []serializer.PackProduct
|
||||
groups []serializer.GroupProducts
|
||||
)
|
||||
if err := json.Unmarshal([]byte(options["pack_data"]), &packs); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := json.Unmarshal([]byte(options["group_sell_data"]), &groups); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return packs, groups, nil
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package vas
|
||||
|
||||
import (
|
||||
model "github.com/HFO4/cloudreve/models"
|
||||
"github.com/HFO4/cloudreve/pkg/serializer"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// GeneralVASService 通用增值服务
|
||||
type GeneralVASService struct {
|
||||
}
|
||||
|
||||
// Quota 获取容量配额信息
|
||||
func (service *GeneralVASService) Quota(c *gin.Context, user *model.User) serializer.Response {
|
||||
packs := user.GetAvailableStoragePacks()
|
||||
return serializer.BuildUserQuotaResponse(user, packs)
|
||||
}
|
Loading…
Reference in new issue