Feat: vas alipay

pull/247/head
HFO4 5 years ago
parent 41f9f3497f
commit f8c14b3826

@ -28,6 +28,7 @@ require (
github.com/qiniu/api.v7/v7 v7.4.0 github.com/qiniu/api.v7/v7 v7.4.0
github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1 github.com/rafaeljusto/redigomock v0.0.0-20191117212112-00b2509252a1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/smartwalle/alipay/v3 v3.0.13
github.com/smartystreets/goconvey v1.6.4 // indirect github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/speps/go-hashids v2.0.0+incompatible github.com/speps/go-hashids v2.0.0+incompatible
github.com/stretchr/testify v1.4.0 github.com/stretchr/testify v1.4.0

@ -173,6 +173,11 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartwalle/alipay v1.0.2 h1:y/uC+6OcVDVxKJ5Bs1qi6aogjDEXwD3XswDXJ5zJurk=
github.com/smartwalle/alipay/v3 v3.0.13 h1:f1Cdnxh6TfbaziLw0i/4h+f8tw9RJwG8y4xye7vTTgY=
github.com/smartwalle/alipay/v3 v3.0.13/go.mod h1:cZUMCCnsux9YAxA0/f3PWUR+7wckWtE1BqxbVRtGij0=
github.com/smartwalle/crypto4go v1.0.2 h1:9DUEOOsPhmp00438L4oBdcL8EZG1zumecft5bWj5phI=
github.com/smartwalle/crypto4go v1.0.2/go.mod h1:LQ7vCZIb7BE5+MuMtJBuO8ORkkQ01m4DXDBWPzLbkMY=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw= github.com/speps/go-hashids v2.0.0+incompatible h1:kSfxGfESueJKTx0mpER9Y/1XHl+FVQjtCqRyYcviFbw=
@ -200,6 +205,7 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=

@ -45,3 +45,15 @@ func (order *Order) Create() (uint, error) {
} }
return order.ID, nil return order.ID, nil
} }
// UpdateStatus 更新订单状态
func (order *Order) UpdateStatus(status int) {
DB.Model(order).Update("status", status)
}
// GetOrderByNo 根据商户订单号查询订单
func GetOrderByNo(id string) (*Order, error) {
var order Order
err := DB.Where("order_no = ?", id).First(&order).Error
return &order, err
}

@ -0,0 +1,42 @@
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
}

@ -4,6 +4,9 @@ import (
"fmt" "fmt"
model "github.com/HFO4/cloudreve/models" model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"github.com/smartwalle/alipay/v3"
"math/rand"
"time"
) )
var ( var (
@ -23,6 +26,12 @@ var (
ErrGroupInvalid = serializer.NewError(serializer.CodeNoPermissionErr, "用户组不可用", nil) ErrGroupInvalid = serializer.NewError(serializer.CodeNoPermissionErr, "用户组不可用", nil)
// ErrUpgradeGroup 用户组冲突 // ErrUpgradeGroup 用户组冲突
ErrUpgradeGroup = serializer.NewError(serializer.CodeDBError, "无法升级用户组", nil) 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 支付处理接口 // Pay 支付处理接口
@ -33,15 +42,38 @@ type Pay interface {
// OrderCreateRes 订单创建结果 // OrderCreateRes 订单创建结果
type OrderCreateRes struct { type OrderCreateRes struct {
Payment bool `json:"payment"` // 是否需要支付 Payment bool `json:"payment"` // 是否需要支付
ID string `json:"id,omitempty"` // 订单号
QRCode string `json:"qr_code,omitempty"` // 支付二维码指向的地址
Redirect string `json:"redirect,omitempty"` // 支付跳转连接,和二维码二选一,不需要的留空
} }
// NewPaymentInstance 获取新的支付实例 // NewPaymentInstance 获取新的支付实例
func NewPaymentInstance(method string) (Pay, error) { func NewPaymentInstance(method string) (Pay, error) {
if method == "score" { switch method {
case "score":
return &ScorePayment{}, nil 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
default:
return nil, ErrUnknownPaymentMethod return nil, ErrUnknownPaymentMethod
}
} }
// NewOrder 创建新订单 // NewOrder 创建新订单
@ -78,6 +110,7 @@ func NewOrder(pack *serializer.PackProduct, group *serializer.GroupProducts, num
// 创建订单记录 // 创建订单记录
order := &model.Order{ order := &model.Order{
UserID: user.ID, UserID: user.ID,
OrderNo: orderID(),
Type: orderType, Type: orderType,
Method: method, Method: method,
ProductID: productID, ProductID: productID,
@ -89,3 +122,10 @@ func NewOrder(pack *serializer.PackProduct, group *serializer.GroupProducts, num
return pay.Create(order, pack, group, user) return pay.Create(order, pack, group, user)
} }
func orderID() string {
return fmt.Sprintf("%s%d%d",
time.Now().Format("20060102150405"),
10000+rand.Intn(90000),
time.Now().UnixNano())
}

@ -1,6 +1,8 @@
package payment package payment
import ( import (
"encoding/json"
"errors"
model "github.com/HFO4/cloudreve/models" model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/serializer" "github.com/HFO4/cloudreve/pkg/serializer"
"time" "time"
@ -69,3 +71,58 @@ func GiveProduct(user *model.User, pack *serializer.PackProduct, group *serializ
return GiveScore(user, num) 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,8 +1,11 @@
package controllers package controllers
import ( import (
"github.com/HFO4/cloudreve/pkg/payment"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/HFO4/cloudreve/service/vas" "github.com/HFO4/cloudreve/service/vas"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/smartwalle/alipay/v3"
) )
// GetQuota 获取容量配额信息 // GetQuota 获取容量配额信息
@ -38,6 +41,17 @@ func NewOrder(c *gin.Context) {
} }
} }
// 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 获取兑换码信息 // GetRedeemInfo 获取兑换码信息
func GetRedeemInfo(c *gin.Context) { func GetRedeemInfo(c *gin.Context) {
var service vas.RedeemService var service vas.RedeemService
@ -59,3 +73,30 @@ func DoRedeem(c *gin.Context) {
c.JSON(200, ErrorResponse(err)) 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)
}

@ -127,6 +127,11 @@ func InitMasterRouter() *gin.Engine {
// 回调接口 // 回调接口
callback := v3.Group("callback") callback := v3.Group("callback")
{ {
// 支付宝回调
callback.POST(
"alipay",
controllers.AlipayCallback,
)
// 远程策略上传回调 // 远程策略上传回调
callback.POST( callback.POST(
"remote/:key", "remote/:key",
@ -363,6 +368,8 @@ func InitMasterRouter() *gin.Engine {
vas.GET("product", controllers.GetProduct) vas.GET("product", controllers.GetProduct)
// 新建支付订单 // 新建支付订单
vas.POST("order", controllers.NewOrder) vas.POST("order", controllers.NewOrder)
// 查询订单状态
vas.GET("order/:id", controllers.OrderStatus)
// 获取兑换码信息 // 获取兑换码信息
vas.GET("redeem/:code", controllers.GetRedeemInfo) vas.GET("redeem/:code", controllers.GetRedeemInfo)
// 执行兑换 // 执行兑换

@ -21,6 +21,21 @@ type RedeemService struct {
Code string `uri:"code" binding:"required,max=64"` 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 开始兑换 // Redeem 开始兑换
func (service *RedeemService) Redeem(c *gin.Context, user *model.User) serializer.Response { func (service *RedeemService) Redeem(c *gin.Context, user *model.User) serializer.Response {
redeem, err := model.GetAvailableRedeem(service.Code) redeem, err := model.GetAvailableRedeem(service.Code)

Loading…
Cancel
Save