membership_plans 表、admin 套餐页、Grant/CreateOrder 读表; Loop continuous 自动 Approve/Closed。Next:ECR-015 RedemptionCode。 Co-authored-by: Cursor <cursoragent@cursor.com>
87 lines
2.2 KiB
Go
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
|
|
}
|
|
}
|