move api/server to api/edge/bff
This commit is contained in:
199
api/edge/bff/internal/server/paymentapiimp/list.go
Normal file
199
api/edge/bff/internal/server/paymentapiimp/list.go
Normal file
@@ -0,0 +1,199 @@
|
||||
package paymentapiimp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
const maxInt32 = int64(1<<31 - 1)
|
||||
|
||||
func (a *PaymentAPI) listPayments(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
||||
orgRef, err := a.oph.GetRef(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to parse organization reference for payments list", zap.Error(err), mutil.PLog(a.oph, r))
|
||||
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
allowed, err := a.enf.Enforce(ctx, a.permissionRef, account.ID, orgRef, bson.NilObjectID, model.ActionRead)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check payments access permissions", zap.Error(err), mutil.PLog(a.oph, r))
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if !allowed {
|
||||
a.logger.Debug("Access denied when listing payments", mutil.PLog(a.oph, r))
|
||||
return response.AccessDenied(a.logger, a.Name(), "payments read permission denied")
|
||||
}
|
||||
|
||||
req := &orchestrationv2.ListPaymentsRequest{Meta: requestMeta(orgRef.Hex(), "")}
|
||||
|
||||
if page, err := listPaymentsPage(r); err != nil {
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
} else if page != nil {
|
||||
req.Page = page
|
||||
}
|
||||
|
||||
query := r.URL.Query()
|
||||
if quotationRef := firstNonEmpty(query.Get("quotation_ref"), query.Get("quote_ref")); quotationRef != "" {
|
||||
req.QuotationRef = quotationRef
|
||||
}
|
||||
createdFrom, err := parseRFC3339Timestamp(firstNonEmpty(query.Get("created_from"), query.Get("createdFrom")), "created_from")
|
||||
if err != nil {
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if createdFrom != nil {
|
||||
req.CreatedFrom = createdFrom
|
||||
}
|
||||
createdTo, err := parseRFC3339Timestamp(firstNonEmpty(query.Get("created_to"), query.Get("createdTo")), "created_to")
|
||||
if err != nil {
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
}
|
||||
if createdTo != nil {
|
||||
req.CreatedTo = createdTo
|
||||
}
|
||||
if req.GetCreatedFrom() != nil && req.GetCreatedTo() != nil {
|
||||
if !req.GetCreatedTo().AsTime().After(req.GetCreatedFrom().AsTime()) {
|
||||
return response.Auto(a.logger, a.Name(), merrors.InvalidArgument("created_to must be after created_from", "created_to"))
|
||||
}
|
||||
}
|
||||
|
||||
if states, err := parsePaymentStateFilters(r); err != nil {
|
||||
return response.Auto(a.logger, a.Name(), err)
|
||||
} else if len(states) > 0 {
|
||||
req.States = states
|
||||
}
|
||||
|
||||
resp, err := a.execution.ListPayments(ctx, req)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to list payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef))
|
||||
return grpcErrorResponse(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
return sresponse.PaymentsListResponse(a.logger, resp, token)
|
||||
}
|
||||
|
||||
func listPaymentsPage(r *http.Request) (*paginationv1.CursorPageRequest, error) {
|
||||
query := r.URL.Query()
|
||||
cursor := strings.TrimSpace(query.Get("cursor"))
|
||||
limitRaw := strings.TrimSpace(query.Get("limit"))
|
||||
|
||||
var limit int64
|
||||
hasLimit := false
|
||||
if limitRaw != "" {
|
||||
parsed, err := strconv.ParseInt(limitRaw, 10, 32)
|
||||
if err != nil {
|
||||
return nil, merrors.InvalidArgument("invalid limit", "limit")
|
||||
}
|
||||
limit = parsed
|
||||
hasLimit = true
|
||||
}
|
||||
|
||||
if cursor == "" && !hasLimit {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
page := &paginationv1.CursorPageRequest{
|
||||
Cursor: cursor,
|
||||
}
|
||||
if hasLimit {
|
||||
if limit < 0 {
|
||||
limit = 0
|
||||
} else if limit > maxInt32 {
|
||||
limit = maxInt32
|
||||
}
|
||||
page.Limit = int32(limit)
|
||||
}
|
||||
|
||||
return page, nil
|
||||
}
|
||||
|
||||
func parsePaymentStateFilters(r *http.Request) ([]orchestrationv2.OrchestrationState, error) {
|
||||
query := r.URL.Query()
|
||||
values := append([]string{}, query["state"]...)
|
||||
values = append(values, query["states"]...)
|
||||
values = append(values, query["filter_states"]...)
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
states := make([]orchestrationv2.OrchestrationState, 0, len(values))
|
||||
for _, raw := range values {
|
||||
for _, part := range strings.Split(raw, ",") {
|
||||
trimmed := strings.TrimSpace(part)
|
||||
if trimmed == "" {
|
||||
continue
|
||||
}
|
||||
state, ok := orchestrationStateFromString(trimmed)
|
||||
if !ok {
|
||||
return nil, merrors.InvalidArgument("unsupported payment state: "+trimmed, "state")
|
||||
}
|
||||
states = append(states, state)
|
||||
}
|
||||
}
|
||||
|
||||
if len(states) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return states, nil
|
||||
}
|
||||
|
||||
func orchestrationStateFromString(value string) (orchestrationv2.OrchestrationState, bool) {
|
||||
upper := strings.ToUpper(strings.TrimSpace(value))
|
||||
if upper == "" {
|
||||
return 0, false
|
||||
}
|
||||
switch upper {
|
||||
case "PAYMENT_STATE_ACCEPTED", "ACCEPTED":
|
||||
return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_CREATED, true
|
||||
case "PAYMENT_STATE_FUNDS_RESERVED", "FUNDS_RESERVED", "PAYMENT_STATE_SUBMITTED", "SUBMITTED":
|
||||
return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_EXECUTING, true
|
||||
case "PAYMENT_STATE_SETTLED", "SETTLED":
|
||||
return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_SETTLED, true
|
||||
case "PAYMENT_STATE_FAILED", "FAILED", "PAYMENT_STATE_CANCELLED", "CANCELLED":
|
||||
return orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_FAILED, true
|
||||
}
|
||||
if !strings.HasPrefix(upper, "ORCHESTRATION_STATE_") {
|
||||
upper = "ORCHESTRATION_STATE_" + upper
|
||||
}
|
||||
enumValue, ok := orchestrationv2.OrchestrationState_value[upper]
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
return orchestrationv2.OrchestrationState(enumValue), true
|
||||
}
|
||||
|
||||
func firstNonEmpty(values ...string) string {
|
||||
for _, value := range values {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseRFC3339Timestamp(raw string, field string) (*timestamppb.Timestamp, error) {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed == "" {
|
||||
return nil, nil
|
||||
}
|
||||
parsed, err := time.Parse(time.RFC3339, trimmed)
|
||||
if err != nil {
|
||||
return nil, merrors.InvalidArgument("invalid "+field+", expected RFC3339", field)
|
||||
}
|
||||
return timestamppb.New(parsed), nil
|
||||
}
|
||||
Reference in New Issue
Block a user