package paymentapiimp import ( "encoding/json" "net/http" "strings" "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" quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2" "github.com/tech/sendico/server/interface/api/srequest" "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" ) func (a *PaymentAPI) quotePayment(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 quote", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(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.ActionCreate) 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 quoting payment", mutil.PLog(a.oph, r)) return response.AccessDenied(a.logger, a.Name(), "payments write permission denied") } payload, err := decodeQuotePayload(r) if err != nil { a.logger.Debug("Failed to decode payload", zap.Error(err), mutil.PLog(a.oph, r)) return response.BadPayload(a.logger, a.Name(), err) } if err := payload.Validate(); err != nil { a.logger.Debug("Failed to validate payload", zap.Error(err), mutil.PLog(a.oph, r)) return response.Auto(a.logger, a.Name(), err) } applyCustomerIP(&payload.Intent, r.RemoteAddr) intent, err := mapQuoteIntent(&payload.Intent) if err != nil { a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r)) return response.BadPayload(a.logger, a.Name(), err) } req := "ationv2.QuotePaymentRequest{ Meta: requestMeta(orgRef.Hex(), payload.IdempotencyKey), IdempotencyKey: payload.IdempotencyKey, Intent: intent, PreviewOnly: payload.PreviewOnly, InitiatorRef: initiatorRef(account), } resp, err := a.quotation.QuotePayment(ctx, req) if err != nil { a.logger.Warn("Failed to quote payment", zap.Error(err), mzap.ObjRef("organization_ref", orgRef)) return grpcErrorResponse(a.logger, a.Name(), err) } return sresponse.PaymentQuoteResponse(a.logger, resp.GetIdempotencyKey(), resp.GetQuote(), token) } func (a *PaymentAPI) quotePayments(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 quotes", zap.Error(err), zap.String(a.oph.Name(), a.oph.GetID(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.ActionCreate) 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 quoting payments", mutil.PLog(a.oph, r)) return response.AccessDenied(a.logger, a.Name(), "payments write permission denied") } payload, err := decodeQuotePaymentsPayload(r) if err != nil { a.logger.Debug("Failed to decode payload", zap.Error(err), mutil.PLog(a.oph, r)) return response.BadPayload(a.logger, a.Name(), err) } if err := payload.Validate(); err != nil { a.logger.Debug("Failed to validate payload", zap.Error(err), mutil.PLog(a.oph, r)) return response.Auto(a.logger, a.Name(), err) } intents := make([]*quotationv2.QuoteIntent, 0, len(payload.Intents)) for i := range payload.Intents { applyCustomerIP(&payload.Intents[i], r.RemoteAddr) intent, err := mapQuoteIntent(&payload.Intents[i]) if err != nil { a.logger.Debug("Failed to map payment intent", zap.Error(err), mutil.PLog(a.oph, r)) return response.BadPayload(a.logger, a.Name(), err) } intents = append(intents, intent) } req := "ationv2.QuotePaymentsRequest{ Meta: requestMeta(orgRef.Hex(), payload.IdempotencyKey), IdempotencyKey: payload.IdempotencyKey, Intents: intents, PreviewOnly: payload.PreviewOnly, InitiatorRef: initiatorRef(account), } resp, err := a.quotation.QuotePayments(ctx, req) if err != nil { a.logger.Warn("Failed to quote payments", zap.Error(err), mzap.ObjRef("organization_ref", orgRef)) return grpcErrorResponse(a.logger, a.Name(), err) } return sresponse.PaymentQuotesResponse(a.logger, resp, token) } func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) { defer r.Body.Close() payload := &srequest.QuotePayment{} if err := json.NewDecoder(r.Body).Decode(payload); err != nil { return nil, merrors.InvalidArgument("invalid payload: "+err.Error(), "payload") } payload.IdempotencyKey = strings.TrimSpace(payload.IdempotencyKey) if err := payload.Validate(); err != nil { return nil, err } return payload, nil } func decodeQuotePaymentsPayload(r *http.Request) (*srequest.QuotePayments, error) { defer r.Body.Close() payload := &srequest.QuotePayments{} if err := json.NewDecoder(r.Body).Decode(payload); err != nil { return nil, merrors.InvalidArgument("invalid payload: "+err.Error(), "payload") } payload.IdempotencyKey = strings.TrimSpace(payload.IdempotencyKey) if err := payload.Validate(); err != nil { return nil, err } return payload, nil }