feat: subscribe and alipay
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"crm/models"
|
||||
"crm/response"
|
||||
"crm/service"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SubscribeApi struct {
|
||||
subscribeService *service.SubscribeService
|
||||
}
|
||||
|
||||
func NewSubscribeApi() *SubscribeApi {
|
||||
subscribeApi := SubscribeApi{
|
||||
subscribeService: &service.SubscribeService{},
|
||||
}
|
||||
return &subscribeApi
|
||||
}
|
||||
|
||||
// 订阅专业版,发起支付
|
||||
func (s *SubscribeApi) Pay(context *gin.Context) {
|
||||
var param models.SubscribePayParam
|
||||
uid, _ := strconv.Atoi(context.Request.Header.Get("uid"))
|
||||
err := context.ShouldBind(¶m);
|
||||
if int64(uid) <= 0 || err != nil {
|
||||
response.Result(response.ErrCodeParamInvalid, nil, context)
|
||||
return
|
||||
}
|
||||
param.Uid = int64(uid)
|
||||
payUrl, errCode := s.subscribeService.Pay(param)
|
||||
response.Result(errCode, payUrl, context)
|
||||
}
|
||||
|
||||
// 支付成功回调
|
||||
func (s *SubscribeApi) Callback(context *gin.Context) {
|
||||
context.Request.ParseForm()
|
||||
var outTradeNo = context.Request.Form.Get("out_trade_no")
|
||||
paySuccessURL, _ := s.subscribeService.Callback(outTradeNo)
|
||||
context.Redirect(http.StatusMovedPermanently, paySuccessURL)
|
||||
}
|
||||
|
||||
// 支付通知
|
||||
func (s *SubscribeApi) Notify(context *gin.Context) {
|
||||
context.Request.ParseForm()
|
||||
var outTradeNo = context.Request.Form.Get("out_trade_no")
|
||||
errCode := s.subscribeService.Notify(context.Request.Form, outTradeNo)
|
||||
response.Result(errCode, nil, context)
|
||||
}
|
||||
|
||||
// 获取订阅信息
|
||||
func (s *SubscribeApi) GetInfo(context *gin.Context) {
|
||||
uid, _ := strconv.Atoi(context.Request.Header.Get("uid"))
|
||||
if int64(uid) <= 0 {
|
||||
response.Result(response.ErrCodeParamInvalid, nil, context)
|
||||
return
|
||||
}
|
||||
subscribeInfo, errCode := s.subscribeService.GetInfo(int64(uid))
|
||||
response.Result(errCode, subscribeInfo, context)
|
||||
}
|
||||
@@ -18,16 +18,16 @@ func Router() {
|
||||
route := engine.Group("/api")
|
||||
|
||||
{
|
||||
// 用户模块
|
||||
// 用户模块,订阅模块
|
||||
route.GET("/user/verifycode", api.NewUserApi().GetVerifyCode)
|
||||
route.GET("/user/info", api.NewUserApi().GetInfo)
|
||||
route.PUT("/user/mail", api.NewUserApi().UpdateMail)
|
||||
route.PUT("/user/buy", api.NewUserApi().Buy)
|
||||
route.POST("/user/login", api.NewUserApi().Login)
|
||||
route.POST("/user/register", api.NewUserApi().Register)
|
||||
route.POST("/user/pass", api.NewUserApi().ForgotPass)
|
||||
route.DELETE("/user/logout", api.NewUserApi().Logout)
|
||||
route.DELETE("/user/delete", api.NewUserApi().Delete)
|
||||
route.GET("/subscribe/callback", api.NewSubscribeApi().Callback)
|
||||
|
||||
// Jwt中间件
|
||||
route.Use(middleware.JwtAuth())
|
||||
@@ -57,7 +57,11 @@ func Router() {
|
||||
|
||||
// 仪表盘模块
|
||||
route.GET("/dashboard/sum", api.NewDashboardApi().Summary)
|
||||
|
||||
|
||||
// 订阅模块
|
||||
route.GET("/subscribe/info", api.NewSubscribeApi().GetInfo)
|
||||
route.POST("/subscribe/pay", api.NewSubscribeApi().Pay)
|
||||
route.POST("/subscribe/notify", api.NewSubscribeApi().Notify)
|
||||
}
|
||||
|
||||
// 启动、监听端口
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package models
|
||||
|
||||
type Subscribe struct {
|
||||
Id int64 `gorm:"primaryKey"`
|
||||
Uid int64 `gorm:"uid"`
|
||||
Version int `gorm:"version"`
|
||||
Expired int64 `gorm:"expired"`
|
||||
Created int64 `gorm:"created"`
|
||||
Updated int64 `gorm:"updated"`
|
||||
}
|
||||
|
||||
type SubscribePayParam struct {
|
||||
Uid int64 `json:"uid" binding:"-"`
|
||||
Version int `json:"version" binding:"required,oneof=2 3"`
|
||||
}
|
||||
|
||||
type SubscribePayOrder struct {
|
||||
Uid int64 `json:"uid"`
|
||||
Version int `json:"version"`
|
||||
}
|
||||
|
||||
type SubscribePayUrl struct {
|
||||
PayUrl string `json:"payUrl"`
|
||||
}
|
||||
|
||||
type SubscribeInfo struct {
|
||||
Version int `json:"version"`
|
||||
Expired int64 `json:"expired"`
|
||||
}
|
||||
@@ -16,6 +16,8 @@ const (
|
||||
ErrCodeCompanyIdNotExist = 10007 // 企业编号不存在
|
||||
ErrCodeEmailFormatInvalid = 10008 // 邮箱格式无效
|
||||
ErrCodeUserPassResetFailed = 10009 // 用户密码重置失败
|
||||
|
||||
ErrCodePayFailed = 20001 // 支付宝支付失败
|
||||
)
|
||||
|
||||
var msg = map[int]string{
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crm/global"
|
||||
"crm/models"
|
||||
"crm/response"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/smartwalle/alipay/v3"
|
||||
"github.com/smartwalle/xid"
|
||||
)
|
||||
|
||||
const (
|
||||
Paying = 1 // 支付中
|
||||
Payed = 2 // 已支付
|
||||
)
|
||||
|
||||
type SubscribeService struct {
|
||||
}
|
||||
|
||||
// 订阅专业版,发起支付
|
||||
func (s *SubscribeService) Pay(param models.SubscribePayParam) (*models.SubscribePayUrl, int) {
|
||||
|
||||
// 构建订单支付信息
|
||||
tradeNo := fmt.Sprintf("%d", xid.Next())
|
||||
var p = alipay.TradePagePay{}
|
||||
p.NotifyURL = global.Config.Alipay.NotifyURL
|
||||
p.ReturnURL = global.Config.Alipay.ReturnURL
|
||||
fmt.Println(p.ReturnURL)
|
||||
p.Subject = "支付测试:" + tradeNo
|
||||
p.OutTradeNo = tradeNo
|
||||
p.ProductCode = "FAST_INSTANT_TRADE_PAY"
|
||||
switch param.Version {
|
||||
case 2:
|
||||
p.TotalAmount = "18.00"
|
||||
case 3:
|
||||
p.TotalAmount = "198.00"
|
||||
}
|
||||
|
||||
// 缓存订单信息
|
||||
order, _ := json.Marshal(&models.SubscribePayOrder{
|
||||
Uid: param.Uid,
|
||||
Version: param.Version,
|
||||
})
|
||||
err := global.Rdb.Set(ctx, tradeNo, string(order), time.Minute*30).Err()
|
||||
if err != nil {
|
||||
return nil, response.ErrCodeFailed
|
||||
}
|
||||
|
||||
// 设置支付状态
|
||||
key := fmt.Sprintf("uid:%v:pay:status", param.Uid)
|
||||
if err := global.Rdb.Set(ctx, key, Paying, time.Minute*10).Err(); err != nil {
|
||||
return nil, response.ErrCodeFailed
|
||||
}
|
||||
|
||||
// 返回支付链接
|
||||
payUrl, err := global.Alipay.TradePagePay(p)
|
||||
if err != nil {
|
||||
return nil, response.ErrCodeFailed
|
||||
}
|
||||
subscribePayUrl := models.SubscribePayUrl{
|
||||
PayUrl: payUrl.String(),
|
||||
}
|
||||
return &subscribePayUrl, response.ErrCodeSuccess
|
||||
}
|
||||
|
||||
// 支付成功回调
|
||||
func (s *SubscribeService) Callback(outTradeNo string) (string, int) {
|
||||
|
||||
// 验证订单信息
|
||||
var p = alipay.TradeQuery{}
|
||||
p.OutTradeNo = outTradeNo
|
||||
rsp, err := global.Alipay.TradeQuery(p)
|
||||
if err != nil {
|
||||
log.Printf("验证订单 %s 信息发生错误: %s", outTradeNo, err.Error())
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
if !rsp.IsSuccess() {
|
||||
log.Printf("验证订单 %s 信息发生错误: %s-%s", outTradeNo, rsp.Content.Msg, rsp.Content.SubMsg)
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
|
||||
// 获取订单信息
|
||||
var order models.SubscribePayOrder
|
||||
orderJson, err := global.Rdb.Get(ctx, outTradeNo).Result()
|
||||
if err != nil {
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
if err := json.Unmarshal([]byte(orderJson), &order); err != nil {
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
|
||||
// 创建订阅信息
|
||||
var expired int64
|
||||
switch order.Version {
|
||||
case 2:
|
||||
expired = time.Now().Unix() + int64(2592000)
|
||||
case 3:
|
||||
expired = time.Now().Unix() + int64(31104000)
|
||||
}
|
||||
subscribe := models.Subscribe{
|
||||
Version: order.Version,
|
||||
Expired: expired,
|
||||
}
|
||||
if global.Db.Table(SUBSCRIBE).Where("uid = ?", order.Uid).First(&models.Subscribe{}).RowsAffected == 0 {
|
||||
subscribe.Uid = order.Uid
|
||||
subscribe.Created = time.Now().Unix()
|
||||
err := global.Db.Table(SUBSCRIBE).Create(&subscribe).Error
|
||||
if err != nil {
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
} else {
|
||||
subscribe.Updated = time.Now().Unix()
|
||||
err = global.Db.Model(&models.Subscribe{}).Where("uid = ?", order.Uid).Updates(&subscribe).Error
|
||||
if err != nil {
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
}
|
||||
|
||||
// 修改支付状态
|
||||
key := fmt.Sprintf("uid:%v:pay:status", order.Uid)
|
||||
if err := global.Rdb.Set(ctx, key, Payed, time.Hour*10).Err(); err != nil {
|
||||
return StringNull, response.ErrCodeFailed
|
||||
}
|
||||
return global.Config.Alipay.PaySuccessURL, response.ErrCodeSuccess
|
||||
}
|
||||
|
||||
// 异步验签
|
||||
func (s *SubscribeService) Notify(data url.Values, outTradeNo string) int {
|
||||
ok, err := global.Alipay.VerifySign(data)
|
||||
if err != nil {
|
||||
log.Println("异步通知验证签名发生错误", err)
|
||||
return response.ErrCodeFailed
|
||||
}
|
||||
|
||||
if !ok {
|
||||
log.Println("异步通知验证签名未通过")
|
||||
return response.ErrCodeFailed
|
||||
}
|
||||
|
||||
log.Println("异步通知验证签名通过")
|
||||
|
||||
var p = alipay.TradeQuery{}
|
||||
p.OutTradeNo = outTradeNo
|
||||
rsp, err := global.Alipay.TradeQuery(p)
|
||||
if err != nil {
|
||||
log.Printf("异步通知验证订单 %s 信息发生错误: %s \n", outTradeNo, err.Error())
|
||||
return response.ErrCodeFailed
|
||||
}
|
||||
if !rsp.IsSuccess() {
|
||||
log.Printf("异步通知验证订单 %s 信息发生错误: %s-%s \n", outTradeNo, rsp.Content.Msg, rsp.Content.SubMsg)
|
||||
return response.ErrCodeFailed
|
||||
}
|
||||
|
||||
log.Printf("订单 %s 支付成功 \n", outTradeNo)
|
||||
return response.ErrCodeSuccess
|
||||
}
|
||||
|
||||
// 获取订阅信息
|
||||
func (s *SubscribeService) GetInfo(uid int64) (*models.SubscribeInfo, int) {
|
||||
var si models.SubscribeInfo
|
||||
if err := global.Db.Table(SUBSCRIBE).First(&si, "uid = ?", uid).Error; err != nil {
|
||||
return nil, response.ErrCodeFailed
|
||||
}
|
||||
// 判断用户订阅是否过期
|
||||
if si.Version == 2 && time.Now().Unix() > int64(si.Expired) {
|
||||
err := global.Db.Model(&models.Subscribe{}).Where("uid = ?", uid).Update("version", 1).Error
|
||||
if err != nil {
|
||||
return nil, response.ErrCodeFailed
|
||||
}
|
||||
}
|
||||
if err := global.Db.Table(SUBSCRIBE).First(&si, "uid = ?", uid).Error; err != nil {
|
||||
return nil, response.ErrCodeFailed
|
||||
}
|
||||
return &si, response.ErrCodeSuccess
|
||||
}
|
||||
Reference in New Issue
Block a user