// Package membership handles growth membership status and mock commerce orders. package membership import ( "context" "errors" "strings" "time" "github.com/google/uuid" "github.com/yuxingu/digital-psychology/apps/api/internal/repository" ) // Service is membership + order use-cases (extracted from report service). type Service struct { Reports *repository.ReportRepo } // CreateOrderInput for commerce. type CreateOrderInput struct { Kind string Plan string ReportID *uuid.UUID } // CreateOrder starts membership, deep_access, or ask_pack order. func (s *Service) CreateOrder(ctx context.Context, userID uuid.UUID, in CreateOrderInput) (uuid.UUID, error) { if in.Kind != "membership" && in.Kind != "deep_access" && in.Kind != "ask_pack" { return uuid.Nil, errors.New("invalid kind") } if in.Kind == "deep_access" && in.ReportID == nil { return uuid.Nil, errors.New("report_id required") } amount := 990 plan := in.Plan if in.Kind == "membership" { if plan == "" { plan = "month" } a, err := s.Reports.MembershipPlanAmountCents(ctx, plan) if err != nil { return uuid.Nil, err } if a <= 0 { return uuid.Nil, errors.New("invalid membership plan") } amount = a } if in.Kind == "ask_pack" { if plan == "" { plan = "pack10" } amount = repository.AskPackAmountCents(plan) if amount <= 0 || repository.AskPackQuota(plan) <= 0 { return uuid.Nil, errors.New("invalid ask_pack plan") } } return s.Reports.CreateOrder(ctx, userID, in.Kind, plan, in.ReportID, amount) } // Redeem applies a redemption code for the current registered user. func (s *Service) Redeem(ctx context.Context, userID uuid.UUID, code string) (string, error) { code = strings.TrimSpace(code) if code == "" { return "", errors.New("code required") } return s.Reports.RedeemCode(ctx, userID, code) } // PayMock completes mock payment. func (s *Service) PayMock(ctx context.Context, userID, orderID uuid.UUID) error { return s.Reports.PayMock(ctx, userID, orderID) } // Me is the public membership snapshot. type Me struct { Active bool `json:"active"` Plan string `json:"plan,omitempty"` Status string `json:"status"` ExpiresAt *time.Time `json:"expires_at,omitempty"` AskQuotaLeft int `json:"ask_quota_left,omitempty"` } // Get returns current growth membership for the user. func (s *Service) Get(ctx context.Context, userID uuid.UUID) (*Me, error) { row, err := s.Reports.GetMembership(ctx, userID) if err != nil { return nil, err } return &Me{ Active: row.Active, Plan: row.Plan, Status: row.Status, ExpiresAt: row.ExpiresAt, AskQuotaLeft: row.AskQuotaLeft, }, nil }