Files
slogan/styleagent/service/member_service.go
T

129 lines
3.7 KiB
Go

package service
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"slogan-agent/styleagent/consts"
"slogan-agent/styleagent/dao"
"slogan-agent/styleagent/model/entity"
"slogan-agent/styleagent/payment"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
type memberService struct{}
var MemberService = new(memberService)
func (s *memberService) PlanList(ctx context.Context) ([]*entity.MemberPlan, error) {
return dao.MemberPlan.ListEnabled(ctx)
}
type MemberStatus struct {
IsVip bool `json:"is_vip"`
ExpireAt string `json:"expire_at"`
PlanName string `json:"plan_name"`
Benefits []string `json:"benefits"`
}
func (s *memberService) Status(ctx context.Context, userId int64) (*MemberStatus, error) {
st := &MemberStatus{Benefits: make([]string, 0)}
um, err := dao.UserMember.GetByUser(ctx, userId)
if err != nil {
return nil, err
}
if um == nil || um.ExpireAt == nil || um.ExpireAt.Time.Before(time.Now()) {
return st, nil
}
st.IsVip = true
st.ExpireAt = um.ExpireAt.Format("Y-m-d H:i:s")
if plan, _ := dao.MemberPlan.GetOne(ctx, um.PlanId); plan != nil {
st.PlanName = plan.Name
st.Benefits = parseBenefits(plan.Features)
}
return st, nil
}
// CreateOrder 下单:生成业务订单号 → 虎皮棋下单 → 返回支付 URL
func (s *memberService) CreateOrder(ctx context.Context, userId, planId int64) (*entity.PaymentOrder, string, error) {
plan, err := dao.MemberPlan.GetOne(ctx, planId)
if err != nil {
return nil, "", err
}
if plan == nil {
return nil, "", errors.New("套餐不存在")
}
order := &entity.PaymentOrder{
OrderNo: fmt.Sprintf("M%d%d", time.Now().UnixNano()/1e6, userId%1000),
UserId: userId,
PlanId: planId,
AmountFen: plan.PriceFen,
Channel: "alipay",
Status: consts.PayStatusPending,
}
if _, err := dao.PaymentOrder.Insert(ctx, order); err != nil {
return nil, "", err
}
payURL, err := payment.CreateOrder(ctx, order.OrderNo, plan.PriceFen)
if err != nil {
return nil, "", err
}
return order, payURL, nil
}
func (s *memberService) OrderStatus(ctx context.Context, orderNo string) (*entity.PaymentOrder, error) {
return dao.PaymentOrder.GetByOrderNo(ctx, orderNo)
}
// HandlePaidNotify 验签已在 handler 完成;状态机 pending→paid 幂等,成功开通/续期
func (s *memberService) HandlePaidNotify(ctx context.Context, orderNo, tradeNo, notifyRaw string) (string, error) {
order, err := dao.PaymentOrder.GetByOrderNo(ctx, orderNo)
if err != nil {
return "no_order", err
}
if order == nil {
return "no_order", nil
}
ok, err := dao.PaymentOrder.MarkPaid(ctx, orderNo, tradeNo, notifyRaw)
if err != nil {
return "no_order", err
}
if !ok {
return "duplicate", nil // 已是 paid 或已关闭
}
days := 30
if plan, _ := dao.MemberPlan.GetOne(ctx, order.PlanId); plan != nil {
days = plan.DurationDays
}
um, _ := dao.UserMember.GetByUser(ctx, order.UserId)
expireAt := NextExpire(um.ExpireAt, days)
if err := dao.UserMember.Upsert(ctx, order.UserId, order.PlanId, expireAt, consts.MemberSourceVipPay); err != nil {
return "no_order", err
}
g.Log().Infof(ctx, "会员开通成功 user=%d order=%s expire=%s", order.UserId, orderNo, expireAt)
return "ok", nil
}
// NextExpire 续期计算:未过期在原有效期上叠加,过期/无记录从现在起算
func NextExpire(old *gtime.Time, days int) string {
base := time.Now()
if old != nil && old.Time.After(base) {
base = old.Time
}
return base.Add(time.Duration(days) * 24 * time.Hour).Format("2006-01-02 15:04:05")
}
func parseBenefits(features string) []string {
var list []string
_ = json.Unmarshal([]byte(features), &list)
if list == nil {
list = make([]string, 0)
}
return list
}