Files
stream.api/internal/modules/payments/handler.go
2026-03-26 13:02:43 +00:00

145 lines
5.8 KiB
Go

package payments
import (
"context"
"encoding/json"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
appv1 "stream.api/internal/gen/proto/app/v1"
"stream.api/internal/modules/common"
)
type Handler struct {
appv1.UnimplementedPaymentsServiceServer
module *Module
}
var _ appv1.PaymentsServiceServer = (*Handler)(nil)
func NewHandler(module *Module) *Handler { return &Handler{module: module} }
func (h *Handler) CreatePayment(ctx context.Context, req *appv1.CreatePaymentRequest) (*appv1.CreatePaymentResponse, error) {
authResult, err := h.module.runtime.Authenticate(ctx)
if err != nil {
return nil, err
}
planID := strings.TrimSpace(req.GetPlanId())
if planID == "" {
return nil, status.Error(codes.InvalidArgument, "Plan ID is required")
}
if !common.IsAllowedTermMonths(req.GetTermMonths()) {
return nil, status.Error(codes.InvalidArgument, "Term months must be one of 1, 3, 6, or 12")
}
paymentMethod := common.NormalizePaymentMethod(req.GetPaymentMethod())
if paymentMethod == "" {
return nil, status.Error(codes.InvalidArgument, "Payment method must be wallet or topup")
}
result, err := h.module.CreatePayment(ctx, CreatePaymentCommand{UserID: authResult.UserID, PlanID: planID, TermMonths: req.GetTermMonths(), PaymentMethod: paymentMethod, TopupAmount: req.TopupAmount})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to create payment")
}
return presentCreatePaymentResponse(result), nil
}
func (h *Handler) ListPaymentHistory(ctx context.Context, req *appv1.ListPaymentHistoryRequest) (*appv1.ListPaymentHistoryResponse, error) {
authResult, err := h.module.runtime.Authenticate(ctx)
if err != nil {
return nil, err
}
result, err := h.module.ListPaymentHistory(ctx, PaymentHistoryQuery{UserID: authResult.UserID, Page: req.GetPage(), Limit: req.GetLimit()})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to fetch payment history")
}
return presentPaymentHistoryResponse(result), nil
}
func (h *Handler) TopupWallet(ctx context.Context, req *appv1.TopupWalletRequest) (*appv1.TopupWalletResponse, error) {
authResult, err := h.module.runtime.Authenticate(ctx)
if err != nil {
return nil, err
}
result, err := h.module.TopupWallet(ctx, TopupWalletCommand{UserID: authResult.UserID, Amount: req.GetAmount()})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to top up wallet")
}
return presentTopupWalletResponse(result), nil
}
func (h *Handler) DownloadInvoice(ctx context.Context, req *appv1.DownloadInvoiceRequest) (*appv1.DownloadInvoiceResponse, error) {
authResult, err := h.module.runtime.Authenticate(ctx)
if err != nil {
return nil, err
}
result, err := h.module.DownloadInvoice(ctx, DownloadInvoiceQuery{UserID: authResult.UserID, ID: strings.TrimSpace(req.GetId())})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to download invoice")
}
return presentDownloadInvoiceResponse(result), nil
}
func (h *Handler) ListAdminPayments(ctx context.Context, req *appv1.ListAdminPaymentsRequest) (*appv1.ListAdminPaymentsResponse, error) {
result, err := h.module.ListAdminPayments(ctx, ListAdminPaymentsQuery{Page: req.GetPage(), Limit: req.GetLimit(), UserID: strings.TrimSpace(req.GetUserId()), StatusFilter: strings.TrimSpace(req.GetStatus())})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to list payments")
}
return presentListAdminPaymentsResponse(result), nil
}
func (h *Handler) GetAdminPayment(ctx context.Context, req *appv1.GetAdminPaymentRequest) (*appv1.GetAdminPaymentResponse, error) {
result, err := h.module.GetAdminPayment(ctx, GetAdminPaymentQuery{ID: strings.TrimSpace(req.GetId())})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to get payment")
}
return presentGetAdminPaymentResponse(*result), nil
}
func (h *Handler) CreateAdminPayment(ctx context.Context, req *appv1.CreateAdminPaymentRequest) (*appv1.CreateAdminPaymentResponse, error) {
userID := strings.TrimSpace(req.GetUserId())
planID := strings.TrimSpace(req.GetPlanId())
if userID == "" || planID == "" {
return nil, status.Error(codes.InvalidArgument, "User ID and plan ID are required")
}
if !common.IsAllowedTermMonths(req.GetTermMonths()) {
return nil, status.Error(codes.InvalidArgument, "Term months must be one of 1, 3, 6, or 12")
}
paymentMethod := common.NormalizePaymentMethod(req.GetPaymentMethod())
if paymentMethod == "" {
return nil, status.Error(codes.InvalidArgument, "Payment method must be wallet or topup")
}
result, err := h.module.CreateAdminPayment(ctx, CreateAdminPaymentCommand{UserID: userID, PlanID: planID, TermMonths: req.GetTermMonths(), PaymentMethod: paymentMethod, TopupAmount: req.TopupAmount})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to create payment")
}
return presentCreateAdminPaymentResponse(result), nil
}
func (h *Handler) UpdateAdminPayment(ctx context.Context, req *appv1.UpdateAdminPaymentRequest) (*appv1.UpdateAdminPaymentResponse, error) {
result, err := h.module.UpdateAdminPayment(ctx, UpdateAdminPaymentCommand{ID: strings.TrimSpace(req.GetId()), NewStatus: req.GetStatus()})
if err != nil {
return nil, h.handleError(ctx, err, "Failed to update payment")
}
return presentUpdateAdminPaymentResponse(*result), nil
}
func (h *Handler) handleError(ctx context.Context, err error, fallback string) error {
if err == nil {
return nil
}
if validationErr, ok := err.(*PaymentValidationError); ok {
body := validationErr.apiBody()
if encoded, marshalErr := json.Marshal(body); marshalErr == nil {
_ = grpc.SetTrailer(ctx, metadata.Pairs("x-error-body", string(encoded)))
}
return status.Error(codes.Code(validationErr.GRPCCode), validationErr.Message)
}
if _, ok := status.FromError(err); ok {
return err
}
h.module.runtime.Logger().Error(fallback, "error", err)
return status.Error(codes.Internal, fallback)
}