Files
digital-psychology/apps/api/internal/service/admin/membership_plans.go
T
jackyu66gitandCursor 882c01d81a feat(ECR-014): MembershipPlan 套餐配置并 Closed
membership_plans 表、admin 套餐页、Grant/CreateOrder 读表;
Loop continuous 自动 Approve/Closed。Next:ECR-015 RedemptionCode。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-07 18:03:38 +08:00

87 lines
2.2 KiB
Go

package admin
import (
"context"
"encoding/json"
"strings"
"github.com/google/uuid"
"github.com/yuxingu/digital-psychology/apps/api/internal/repository"
)
var (
ErrPlanNotFound = errString("plan not found")
ErrInvalidPlanU = errString("invalid plan update")
)
// ListMembershipPlans returns catalog.
func (s *Service) ListMembershipPlans(ctx context.Context) ([]repository.MembershipPlanRow, error) {
items, err := s.Repo.ListMembershipPlans(ctx)
if err != nil {
return nil, err
}
if items == nil {
items = []repository.MembershipPlanRow{}
}
return items, nil
}
// GetMembershipPlan returns one plan.
func (s *Service) GetMembershipPlan(ctx context.Context, code string) (*repository.MembershipPlanRow, error) {
p, err := s.Repo.GetMembershipPlan(ctx, code)
if err != nil {
return nil, err
}
if p == nil {
return nil, ErrPlanNotFound
}
return p, nil
}
// UpdateMembershipPlan updates mutable fields.
func (s *Service) UpdateMembershipPlan(
ctx context.Context, adminID uuid.UUID, code, title string, days, amount int, active bool,
) (*repository.MembershipPlanRow, error) {
code = strings.TrimSpace(code)
title = strings.TrimSpace(title)
if title == "" || days <= 0 || amount < 0 {
return nil, ErrInvalidPlanU
}
if _, err := s.GetMembershipPlan(ctx, code); err != nil {
return nil, err
}
meta, _ := json.Marshal(map[string]any{
"title": title, "duration_days": days, "amount_cents": amount, "active": active,
})
if err := s.Repo.UpdateMembershipPlanWithAudit(ctx, adminID, code, title, days, amount, active, meta); err != nil {
return nil, err
}
return s.GetMembershipPlan(ctx, code)
}
// PlanDurationDays resolves grant length from catalog with hardcoded fallback.
func (s *Service) PlanDurationDays(ctx context.Context, plan string) (int, error) {
p, err := s.Repo.GetMembershipPlan(ctx, plan)
if err != nil {
return 0, err
}
if p != nil && p.Active && p.DurationDays > 0 {
return p.DurationDays, nil
}
return planDaysFallback(plan)
}
func planDaysFallback(plan string) (int, error) {
switch plan {
case "month":
return 31, nil
case "quarter":
return 92, nil
case "year":
return 366, nil
default:
return 0, ErrInvalidPlan
}
}