354 lines
11 KiB
Go
354 lines
11 KiB
Go
package paymethodsimp
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
methodsclient "github.com/tech/sendico/payments/methods/client"
|
|
api "github.com/tech/sendico/pkg/api/http"
|
|
"github.com/tech/sendico/pkg/api/http/response"
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
"github.com/tech/sendico/pkg/mlogger"
|
|
"github.com/tech/sendico/pkg/model"
|
|
"github.com/tech/sendico/pkg/mservice"
|
|
methodsv1 "github.com/tech/sendico/pkg/proto/payments/methods/v1"
|
|
eapi "github.com/tech/sendico/server/interface/api"
|
|
"github.com/tech/sendico/server/interface/api/sresponse"
|
|
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
type PaymentMethodsAPI struct {
|
|
logger mlogger.Logger
|
|
client methodsclient.Client
|
|
oph mutil.ParamHelper
|
|
rph mutil.ParamHelper
|
|
mph mutil.ParamHelper
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) Name() mservice.Type {
|
|
return mservice.PaymentMethods
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) Finish(_ context.Context) error {
|
|
if a.client != nil {
|
|
return a.client.Close()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func CreateAPI(apiCtx eapi.API) (*PaymentMethodsAPI, error) {
|
|
logger := apiCtx.Logger().Named(mservice.PaymentMethods)
|
|
|
|
cfg := apiCtx.Config().PaymentMethods
|
|
if cfg == nil {
|
|
return nil, merrors.InvalidArgument("payment methods configuration is not provided")
|
|
}
|
|
|
|
address, err := resolveClientAddress("payment methods", cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
clientCfg := methodsclient.Config{
|
|
Address: address,
|
|
DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
|
|
CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
|
|
Insecure: cfg.Insecure,
|
|
}
|
|
|
|
client, err := methodsclient.New(context.Background(), clientCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res := &PaymentMethodsAPI{
|
|
logger: logger,
|
|
client: client,
|
|
oph: mutil.CreatePH(mservice.Organizations),
|
|
rph: mutil.CreatePH(mservice.Recipients),
|
|
mph: mutil.CreatePH(mservice.PaymentMethods),
|
|
}
|
|
|
|
apiCtx.Register().AccountHandler(res.Name(), res.oph.AddRef("/"), api.Post, res.create)
|
|
apiCtx.Register().AccountHandler(res.Name(), res.rph.AddRef(res.oph.AddRef("/list")), api.Get, res.list)
|
|
apiCtx.Register().AccountHandler(res.Name(), res.mph.AddRef("/"), api.Get, res.get)
|
|
apiCtx.Register().AccountHandler(res.Name(), "/", api.Put, res.update)
|
|
apiCtx.Register().AccountHandler(res.Name(), res.mph.AddRef("/"), api.Delete, res.delete)
|
|
apiCtx.Register().AccountHandler(res.Name(), res.mph.AddRef(res.oph.AddRef("/archive")), api.Get, res.archive)
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) create(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
|
orgRef, err := a.oph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
|
}
|
|
|
|
payload, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return response.BadPayload(a.logger, a.Name(), err)
|
|
}
|
|
|
|
resp, err := a.client.CreatePaymentMethod(r.Context(), &methodsv1.CreatePaymentMethodRequest{
|
|
AccountRef: account.ID.Hex(),
|
|
OrganizationRef: orgRef.Hex(),
|
|
PaymentMethodJson: payload,
|
|
})
|
|
if err != nil {
|
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
|
}
|
|
|
|
pm, err := decodePaymentMethod(resp.GetPaymentMethodJson())
|
|
if err != nil {
|
|
return response.Internal(a.logger, a.Name(), err)
|
|
}
|
|
return sresponse.ObjectAuthCreated(a.logger, pm, token, a.Name())
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) list(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
|
orgRef, err := a.oph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
|
}
|
|
recipientRef, err := a.rph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.rph.Name(), a.rph.GetID(r), err)
|
|
}
|
|
|
|
cursor, err := mutil.GetViewCursor(a.logger, r)
|
|
if err != nil {
|
|
return response.Auto(a.logger, a.Name(), err)
|
|
}
|
|
|
|
resp, err := a.client.ListPaymentMethods(r.Context(), &methodsv1.ListPaymentMethodsRequest{
|
|
AccountRef: account.ID.Hex(),
|
|
OrganizationRef: orgRef.Hex(),
|
|
RecipientRef: recipientRef.Hex(),
|
|
Cursor: toProtoCursor(cursor),
|
|
})
|
|
if err != nil {
|
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
|
}
|
|
|
|
items, err := decodePaymentMethods(resp.GetPaymentMethodsJson())
|
|
if err != nil {
|
|
return response.Internal(a.logger, a.Name(), err)
|
|
}
|
|
return sresponse.ObjectsAuth(a.logger, items, token, a.Name())
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) get(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
|
methodRef, err := a.mph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.mph.Name(), a.mph.GetID(r), err)
|
|
}
|
|
|
|
resp, err := a.client.GetPaymentMethod(r.Context(), &methodsv1.GetPaymentMethodRequest{
|
|
AccountRef: account.ID.Hex(),
|
|
PaymentMethodRef: methodRef.Hex(),
|
|
})
|
|
if err != nil {
|
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
|
}
|
|
|
|
pm, err := decodePaymentMethod(resp.GetPaymentMethodJson())
|
|
if err != nil {
|
|
return response.Internal(a.logger, a.Name(), err)
|
|
}
|
|
return sresponse.ObjectAuth(a.logger, pm, token, a.Name())
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) update(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
|
payload, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
return response.BadPayload(a.logger, a.Name(), err)
|
|
}
|
|
|
|
resp, err := a.client.UpdatePaymentMethod(r.Context(), &methodsv1.UpdatePaymentMethodRequest{
|
|
AccountRef: account.ID.Hex(),
|
|
PaymentMethodJson: payload,
|
|
})
|
|
if err != nil {
|
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
|
}
|
|
|
|
pm, err := decodePaymentMethod(resp.GetPaymentMethodJson())
|
|
if err != nil {
|
|
return response.Internal(a.logger, a.Name(), err)
|
|
}
|
|
return sresponse.ObjectAuth(a.logger, pm, token, a.Name())
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) delete(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
|
methodRef, err := a.mph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.mph.Name(), a.mph.GetID(r), err)
|
|
}
|
|
|
|
cascade, err := mutil.GetCascadeParam(a.logger, r)
|
|
if err != nil {
|
|
return response.Auto(a.logger, a.Name(), err)
|
|
}
|
|
|
|
cascadeValue := false
|
|
if cascade != nil {
|
|
cascadeValue = *cascade
|
|
}
|
|
|
|
_, err = a.client.DeletePaymentMethod(r.Context(), &methodsv1.DeletePaymentMethodRequest{
|
|
AccountRef: account.ID.Hex(),
|
|
PaymentMethodRef: methodRef.Hex(),
|
|
Cascade: cascadeValue,
|
|
})
|
|
if err != nil {
|
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
|
}
|
|
|
|
return sresponse.ObjectsAuth(a.logger, []model.PaymentMethod{}, token, a.Name())
|
|
}
|
|
|
|
func (a *PaymentMethodsAPI) archive(r *http.Request, account *model.Account, token *sresponse.TokenData) http.HandlerFunc {
|
|
methodRef, err := a.mph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.mph.Name(), a.mph.GetID(r), err)
|
|
}
|
|
orgRef, err := a.oph.GetRef(r)
|
|
if err != nil {
|
|
return response.BadReference(a.logger, a.Name(), a.oph.Name(), a.oph.GetID(r), err)
|
|
}
|
|
|
|
archived, err := mutil.GetArchiveParam(a.logger, r)
|
|
if err != nil {
|
|
return response.Auto(a.logger, a.Name(), err)
|
|
}
|
|
if archived == nil {
|
|
return response.BadRequest(a.logger, a.Name(), "invalid_query_parameter", "'archived' param must be present")
|
|
}
|
|
|
|
cascade, err := mutil.GetCascadeParam(a.logger, r)
|
|
if err != nil {
|
|
return response.Auto(a.logger, a.Name(), err)
|
|
}
|
|
cascadeValue := false
|
|
if cascade != nil {
|
|
cascadeValue = *cascade
|
|
}
|
|
|
|
_, err = a.client.SetPaymentMethodArchived(r.Context(), &methodsv1.SetPaymentMethodArchivedRequest{
|
|
AccountRef: account.ID.Hex(),
|
|
OrganizationRef: orgRef.Hex(),
|
|
PaymentMethodRef: methodRef.Hex(),
|
|
Archived: *archived,
|
|
Cascade: cascadeValue,
|
|
})
|
|
if err != nil {
|
|
return grpcErrorResponse(a.logger, a.Name(), err)
|
|
}
|
|
|
|
return sresponse.ObjectsAuth(a.logger, []model.PaymentMethod{}, token, a.Name())
|
|
}
|
|
|
|
func resolveClientAddress(service string, cfg *eapi.PaymentOrchestratorConfig) (string, error) {
|
|
if cfg == nil {
|
|
return "", merrors.InvalidArgument(strings.TrimSpace(service) + " configuration is not provided")
|
|
}
|
|
address := strings.TrimSpace(cfg.Address)
|
|
if address != "" {
|
|
return address, nil
|
|
}
|
|
if env := strings.TrimSpace(cfg.AddressEnv); env != "" {
|
|
if resolved := strings.TrimSpace(os.Getenv(env)); resolved != "" {
|
|
return resolved, nil
|
|
}
|
|
return "", merrors.InvalidArgument(service + " address is not specified and address env " + env + " is empty")
|
|
}
|
|
return "", merrors.InvalidArgument(strings.TrimSpace(service) + " address is not specified")
|
|
}
|
|
|
|
func toProtoCursor(cursor *model.ViewCursor) *methodsv1.ViewCursor {
|
|
if cursor == nil {
|
|
return nil
|
|
}
|
|
|
|
res := &methodsv1.ViewCursor{}
|
|
hasAny := false
|
|
if cursor.Limit != nil {
|
|
res.Limit = wrapperspb.Int64(*cursor.Limit)
|
|
hasAny = true
|
|
}
|
|
if cursor.Offset != nil {
|
|
res.Offset = wrapperspb.Int64(*cursor.Offset)
|
|
hasAny = true
|
|
}
|
|
if cursor.IsArchived != nil {
|
|
res.IsArchived = wrapperspb.Bool(*cursor.IsArchived)
|
|
hasAny = true
|
|
}
|
|
if !hasAny {
|
|
return nil
|
|
}
|
|
return res
|
|
}
|
|
|
|
func decodePaymentMethod(payload []byte) (*model.PaymentMethod, error) {
|
|
var pm model.PaymentMethod
|
|
if err := json.Unmarshal(payload, &pm); err != nil {
|
|
return nil, err
|
|
}
|
|
return &pm, nil
|
|
}
|
|
|
|
func decodePaymentMethods(items [][]byte) ([]model.PaymentMethod, error) {
|
|
if len(items) == 0 {
|
|
return nil, nil
|
|
}
|
|
res := make([]model.PaymentMethod, 0, len(items))
|
|
for i := range items {
|
|
pm, err := decodePaymentMethod(items[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res = append(res, *pm)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func grpcErrorResponse(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
|
|
statusErr, ok := status.FromError(err)
|
|
if !ok {
|
|
return response.Internal(logger, source, err)
|
|
}
|
|
|
|
switch statusErr.Code() {
|
|
case codes.InvalidArgument:
|
|
return response.BadRequest(logger, source, "invalid_argument", statusErr.Message())
|
|
case codes.NotFound:
|
|
return response.NotFound(logger, source, statusErr.Message())
|
|
case codes.PermissionDenied:
|
|
return response.AccessDenied(logger, source, statusErr.Message())
|
|
case codes.Unauthenticated:
|
|
return response.Unauthorized(logger, source, statusErr.Message())
|
|
case codes.AlreadyExists, codes.Aborted:
|
|
return response.DataConflict(logger, source, statusErr.Message())
|
|
case codes.Unimplemented:
|
|
return response.NotImplemented(logger, source, statusErr.Message())
|
|
case codes.FailedPrecondition:
|
|
return response.Error(logger, source, http.StatusPreconditionFailed, "failed_precondition", statusErr.Message())
|
|
case codes.DeadlineExceeded:
|
|
return response.Error(logger, source, http.StatusGatewayTimeout, "deadline_exceeded", statusErr.Message())
|
|
case codes.Unavailable:
|
|
return response.Error(logger, source, http.StatusServiceUnavailable, "service_unavailable", statusErr.Message())
|
|
default:
|
|
return response.Internal(logger, source, err)
|
|
}
|
|
}
|