314 lines
9.0 KiB
Go
314 lines
9.0 KiB
Go
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/LoveLosita/smartflow/backend/shared/respond"
|
|
"github.com/LoveLosita/smartflow/backend/services/tokenstore/rpc/pb"
|
|
tokenstoresv "github.com/LoveLosita/smartflow/backend/services/tokenstore/sv"
|
|
tokencontracts "github.com/LoveLosita/smartflow/backend/shared/contracts/tokenstore"
|
|
)
|
|
|
|
type Handler struct {
|
|
pb.UnimplementedTokenStoreServiceServer
|
|
svc *tokenstoresv.Service
|
|
}
|
|
|
|
func NewHandler(svc *tokenstoresv.Service) *Handler {
|
|
return &Handler{svc: svc}
|
|
}
|
|
|
|
// service 负责统一校验 RPC 层依赖是否已经注入。
|
|
//
|
|
// 职责边界:
|
|
// 1. 只判断 handler 自身和业务 service 是否可用;
|
|
// 2. 不负责支付状态流转、订单幂等和 grant 账本写入;
|
|
// 3. 失败时返回可直接转成 gRPC status 的业务错误。
|
|
func (h *Handler) service() (*tokenstoresv.Service, error) {
|
|
if h == nil || h.svc == nil {
|
|
return nil, errors.New("tokenstore service dependency not initialized")
|
|
}
|
|
return h.svc, nil
|
|
}
|
|
|
|
// GetSummary 负责把 Token 概览请求从 gRPC 协议转成内部服务调用。
|
|
func (h *Handler) GetSummary(ctx context.Context, req *pb.GetTokenSummaryRequest) (*pb.GetTokenSummaryResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
summary, err := svc.GetSummary(ctx, req.ActorUserId)
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.GetTokenSummaryResponse{Summary: tokenSummaryToPB(summary)}, nil
|
|
}
|
|
|
|
func (h *Handler) ListProducts(ctx context.Context, req *pb.ListTokenProductsRequest) (*pb.ListTokenProductsResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
items, err := svc.ListProducts(ctx, req.ActorUserId)
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.ListTokenProductsResponse{Items: tokenProductsToPB(items)}, nil
|
|
}
|
|
|
|
func (h *Handler) CreateOrder(ctx context.Context, req *pb.CreateTokenOrderRequest) (*pb.CreateTokenOrderResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
order, err := svc.CreateOrder(ctx, tokencontracts.CreateTokenOrderRequest{
|
|
ActorUserID: req.ActorUserId,
|
|
ProductID: req.ProductId,
|
|
Quantity: int(req.Quantity),
|
|
IdempotencyKey: req.IdempotencyKey,
|
|
})
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.CreateTokenOrderResponse{Order: tokenOrderToPB(order)}, nil
|
|
}
|
|
|
|
func (h *Handler) ListOrders(ctx context.Context, req *pb.ListTokenOrdersRequest) (*pb.ListTokenOrdersResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
items, page, err := svc.ListOrders(ctx, tokencontracts.ListTokenOrdersRequest{
|
|
ActorUserID: req.ActorUserId,
|
|
Page: int(req.Page),
|
|
PageSize: int(req.PageSize),
|
|
Status: req.Status,
|
|
})
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.ListTokenOrdersResponse{
|
|
Items: tokenOrdersToPB(items),
|
|
Page: tokenPageToPB(page),
|
|
}, nil
|
|
}
|
|
|
|
func (h *Handler) GetOrder(ctx context.Context, req *pb.GetTokenOrderRequest) (*pb.GetTokenOrderResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
order, err := svc.GetOrder(ctx, req.ActorUserId, req.OrderId)
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.GetTokenOrderResponse{Order: tokenOrderToPB(order)}, nil
|
|
}
|
|
|
|
func (h *Handler) MockPaidOrder(ctx context.Context, req *pb.MockPaidOrderRequest) (*pb.MockPaidOrderResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
order, err := svc.MockPaidOrder(ctx, tokencontracts.MockPaidOrderRequest{
|
|
ActorUserID: req.ActorUserId,
|
|
OrderID: req.OrderId,
|
|
MockChannel: req.MockChannel,
|
|
IdempotencyKey: req.IdempotencyKey,
|
|
})
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.MockPaidOrderResponse{Order: tokenOrderToPB(order)}, nil
|
|
}
|
|
|
|
func (h *Handler) ListGrants(ctx context.Context, req *pb.ListTokenGrantsRequest) (*pb.ListTokenGrantsResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
items, page, err := svc.ListGrants(ctx, tokencontracts.ListTokenGrantsRequest{
|
|
ActorUserID: req.ActorUserId,
|
|
Page: int(req.Page),
|
|
PageSize: int(req.PageSize),
|
|
Source: req.Source,
|
|
})
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.ListTokenGrantsResponse{
|
|
Items: tokenGrantsToPB(items),
|
|
Page: tokenPageToPB(page),
|
|
}, nil
|
|
}
|
|
|
|
// RecordForumRewardGrant 负责把论坛 outbox 奖励事件转成 token-store 内部账本写入调用。
|
|
func (h *Handler) RecordForumRewardGrant(ctx context.Context, req *pb.RecordForumRewardGrantRequest) (*pb.RecordForumRewardGrantResponse, error) {
|
|
svc, err := h.service()
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
if req == nil {
|
|
return nil, grpcErrorFromServiceError(respond.MissingParam)
|
|
}
|
|
|
|
grant, err := svc.RecordForumRewardGrant(ctx, tokencontracts.RecordForumRewardGrantRequest{
|
|
EventID: req.EventId,
|
|
ReceiverUserID: req.ReceiverUserId,
|
|
Source: req.Source,
|
|
SourceRefID: req.SourceRefId,
|
|
})
|
|
if err != nil {
|
|
return nil, grpcErrorFromServiceError(err)
|
|
}
|
|
return &pb.RecordForumRewardGrantResponse{Grant: tokenGrantToPB(grant)}, nil
|
|
}
|
|
|
|
func tokenPageToPB(page tokencontracts.PageResult) *pb.PageResponse {
|
|
return &pb.PageResponse{
|
|
Page: int32(page.Page),
|
|
PageSize: int32(page.PageSize),
|
|
Total: int32(page.Total),
|
|
HasMore: page.HasMore,
|
|
}
|
|
}
|
|
|
|
func tokenSummaryToPB(summary *tokencontracts.TokenSummary) *pb.TokenSummary {
|
|
if summary == nil {
|
|
return nil
|
|
}
|
|
return &pb.TokenSummary{
|
|
RecordedTokenTotal: summary.RecordedTokenTotal,
|
|
AppliedTokenTotal: summary.AppliedTokenTotal,
|
|
PendingApplyTokenTotal: summary.PendingApplyTokenTotal,
|
|
QuotaSyncStatus: summary.QuotaSyncStatus,
|
|
Tip: summary.Tip,
|
|
}
|
|
}
|
|
|
|
func tokenProductToPB(product tokencontracts.TokenProductView) *pb.TokenProductView {
|
|
return &pb.TokenProductView{
|
|
ProductId: product.ProductID,
|
|
Name: product.Name,
|
|
Description: product.Description,
|
|
TokenAmount: product.TokenAmount,
|
|
PriceCent: product.PriceCent,
|
|
PriceText: product.PriceText,
|
|
Currency: product.Currency,
|
|
Badge: product.Badge,
|
|
Status: product.Status,
|
|
SortOrder: int32(product.SortOrder),
|
|
}
|
|
}
|
|
|
|
func tokenProductsToPB(items []tokencontracts.TokenProductView) []*pb.TokenProductView {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
result := make([]*pb.TokenProductView, 0, len(items))
|
|
for i := range items {
|
|
result = append(result, tokenProductToPB(items[i]))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func tokenGrantToPB(grant *tokencontracts.TokenGrantView) *pb.TokenGrantView {
|
|
if grant == nil {
|
|
return nil
|
|
}
|
|
return &pb.TokenGrantView{
|
|
GrantId: grant.GrantID,
|
|
EventId: grant.EventID,
|
|
Source: grant.Source,
|
|
SourceLabel: grant.SourceLabel,
|
|
Amount: grant.Amount,
|
|
Status: grant.Status,
|
|
QuotaApplied: grant.QuotaApplied,
|
|
Description: grant.Description,
|
|
CreatedAt: grant.CreatedAt,
|
|
}
|
|
}
|
|
|
|
func tokenGrantsToPB(items []tokencontracts.TokenGrantView) []*pb.TokenGrantView {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
result := make([]*pb.TokenGrantView, 0, len(items))
|
|
for i := range items {
|
|
item := items[i]
|
|
result = append(result, tokenGrantToPB(&item))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func tokenOrderToPB(order *tokencontracts.TokenOrderView) *pb.TokenOrderView {
|
|
if order == nil {
|
|
return nil
|
|
}
|
|
return &pb.TokenOrderView{
|
|
OrderId: order.OrderID,
|
|
OrderNo: order.OrderNo,
|
|
Status: order.Status,
|
|
TokenAmount: order.TokenAmount,
|
|
AmountCent: order.AmountCent,
|
|
PriceText: order.PriceText,
|
|
Currency: order.Currency,
|
|
PaymentMode: order.PaymentMode,
|
|
Grant: tokenGrantToPB(order.Grant),
|
|
CreatedAt: order.CreatedAt,
|
|
PaidAt: tokenStringFromPtr(order.PaidAt),
|
|
GrantedAt: tokenStringFromPtr(order.GrantedAt),
|
|
ProductSnapshot: order.ProductSnapshot,
|
|
ProductName: order.ProductName,
|
|
Quantity: int32(order.Quantity),
|
|
}
|
|
}
|
|
|
|
func tokenOrdersToPB(items []tokencontracts.TokenOrderView) []*pb.TokenOrderView {
|
|
if len(items) == 0 {
|
|
return nil
|
|
}
|
|
result := make([]*pb.TokenOrderView, 0, len(items))
|
|
for i := range items {
|
|
item := items[i]
|
|
result = append(result, tokenOrderToPB(&item))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func tokenStringFromPtr(value *string) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
return *value
|
|
}
|