new payment methods service
This commit is contained in:
@@ -27,6 +27,7 @@ const (
|
||||
ledgerDebitOperation = "ledger.debit"
|
||||
ledgerCreditOperation = "ledger.credit"
|
||||
gatewayReadBalanceOperation = "balance.read"
|
||||
paymentMethodsReadOperation = "payment_methods.read"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -44,6 +45,11 @@ var (
|
||||
"PAYMENT_QUOTATION",
|
||||
"payment_quotation",
|
||||
}
|
||||
paymentMethodsDiscoveryServiceNames = []string{
|
||||
"PAYMENTS_METHODS",
|
||||
"PAYMENT_METHODS",
|
||||
string(mservice.PaymentMethods),
|
||||
}
|
||||
)
|
||||
|
||||
type discoveryEndpoint struct {
|
||||
@@ -105,6 +111,7 @@ func (a *APIImp) resolveServiceAddressesFromDiscovery() {
|
||||
orchestratorFound, orchestratorEndpoint := a.resolvePaymentOrchestratorAddress(lookup.Services)
|
||||
a.resolveLedgerAddress(lookup.Services)
|
||||
a.resolvePaymentQuotationAddress(lookup.Services, orchestratorFound, orchestratorEndpoint)
|
||||
a.resolvePaymentMethodsAddress(lookup.Services)
|
||||
}
|
||||
|
||||
func (a *APIImp) resolveChainGatewayAddress(gateways []discovery.GatewaySummary) {
|
||||
@@ -218,6 +225,30 @@ func (a *APIImp) resolvePaymentQuotationAddress(services []discovery.ServiceSumm
|
||||
zap.Bool("insecure", endpoint.insecure))
|
||||
}
|
||||
|
||||
func (a *APIImp) resolvePaymentMethodsAddress(services []discovery.ServiceSummary) {
|
||||
endpoint, selected, ok := selectServiceEndpoint(
|
||||
services,
|
||||
paymentMethodsDiscoveryServiceNames,
|
||||
[]string{paymentMethodsReadOperation},
|
||||
)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
cfg := ensurePaymentMethodsConfig(a.config)
|
||||
cfg.Address = endpoint.address
|
||||
cfg.Insecure = endpoint.insecure
|
||||
ensureTimeoutsPayment(cfg)
|
||||
|
||||
a.logger.Info("Resolved payment methods address from discovery",
|
||||
zap.String("service", selected.Service),
|
||||
zap.String("service_id", selected.ID),
|
||||
zap.String("instance_id", selected.InstanceID),
|
||||
zap.String("invoke_uri", endpoint.raw),
|
||||
zap.String("address", endpoint.address),
|
||||
zap.Bool("insecure", endpoint.insecure))
|
||||
}
|
||||
|
||||
func selectServiceEndpoint(services []discovery.ServiceSummary, serviceNames []string, requiredOps []string) (discoveryEndpoint, discovery.ServiceSummary, bool) {
|
||||
selections := make([]serviceSelection, 0)
|
||||
for _, svc := range services {
|
||||
@@ -429,6 +460,16 @@ func ensurePaymentQuotationConfig(cfg *eapi.Config) *eapi.PaymentOrchestratorCon
|
||||
return cfg.PaymentQuotation
|
||||
}
|
||||
|
||||
func ensurePaymentMethodsConfig(cfg *eapi.Config) *eapi.PaymentOrchestratorConfig {
|
||||
if cfg == nil {
|
||||
return nil
|
||||
}
|
||||
if cfg.PaymentMethods == nil {
|
||||
cfg.PaymentMethods = &eapi.PaymentOrchestratorConfig{}
|
||||
}
|
||||
return cfg.PaymentMethods
|
||||
}
|
||||
|
||||
func ensureTimeoutsLedger(cfg *eapi.LedgerConfig) {
|
||||
if cfg == nil {
|
||||
return
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
ledgerclient "github.com/tech/sendico/ledger/client"
|
||||
trongatewayclient "github.com/tech/sendico/gateway/tron/client"
|
||||
api "github.com/tech/sendico/pkg/api/http"
|
||||
"github.com/tech/sendico/pkg/auth"
|
||||
@@ -22,6 +23,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"github.com/tech/sendico/server/interface/accountservice"
|
||||
eapi "github.com/tech/sendico/server/interface/api"
|
||||
"github.com/tech/sendico/server/interface/services/fileservice"
|
||||
@@ -49,6 +51,7 @@ type AccountAPI struct {
|
||||
accountsPermissionRef bson.ObjectID
|
||||
accService accountservice.AccountService
|
||||
chainGateway chainWalletClient
|
||||
ledgerClient ledgerAccountClient
|
||||
chainAsset *chainv1.Asset
|
||||
}
|
||||
|
||||
@@ -57,6 +60,11 @@ type chainWalletClient interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
type ledgerAccountClient interface {
|
||||
CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
func (a *AccountAPI) Name() mservice.Type {
|
||||
return mservice.Accounts
|
||||
}
|
||||
@@ -70,6 +78,11 @@ func (a *AccountAPI) Finish(ctx context.Context) error {
|
||||
a.logger.Warn("Failed to close chain gateway client", zap.Error(err))
|
||||
}
|
||||
}
|
||||
if a.ledgerClient != nil {
|
||||
if err := a.ledgerClient.Close(); err != nil {
|
||||
a.logger.Warn("Failed to close ledger client", zap.Error(err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -158,6 +171,10 @@ func CreateAPI(a eapi.API) (*AccountAPI, error) {
|
||||
p.logger.Error("Failed to initialize chain gateway client", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
if err := p.initLedgerClient(cfg.Ledger); err != nil {
|
||||
p.logger.Error("Failed to initialize ledger client", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
@@ -198,6 +215,35 @@ func (a *AccountAPI) initChainGateway(cfg *eapi.ChainGatewayConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AccountAPI) initLedgerClient(cfg *eapi.LedgerConfig) error {
|
||||
if cfg == nil {
|
||||
return merrors.InvalidArgument("ledger configuration is not provided")
|
||||
}
|
||||
|
||||
address := strings.TrimSpace(cfg.Address)
|
||||
if address == "" {
|
||||
address = strings.TrimSpace(os.Getenv(cfg.AddressEnv))
|
||||
}
|
||||
if address == "" {
|
||||
return merrors.InvalidArgument(fmt.Sprintf("ledger address is not specified and address env %s is empty", cfg.AddressEnv))
|
||||
}
|
||||
|
||||
clientCfg := ledgerclient.Config{
|
||||
Address: address,
|
||||
DialTimeout: time.Duration(cfg.DialTimeoutSeconds) * time.Second,
|
||||
CallTimeout: time.Duration(cfg.CallTimeoutSeconds) * time.Second,
|
||||
Insecure: cfg.Insecure,
|
||||
}
|
||||
|
||||
client, err := ledgerclient.New(context.Background(), clientCfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.ledgerClient = client
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildGatewayAsset(cfg eapi.ChainGatewayAssetConfig) (*chainv1.Asset, error) {
|
||||
chain, err := parseChainNetwork(cfg.Chain)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/mutil/mzap"
|
||||
describablev1 "github.com/tech/sendico/pkg/proto/common/describable/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
"github.com/tech/sendico/server/interface/api/sresponse"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
@@ -199,6 +200,10 @@ func (a *AccountAPI) signupTransactionBody(ctx context.Context, sr *srequest.Sig
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization wallet created", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
if err := a.openOrgLedgerAccount(ctx, org, sr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.logger.Info("Organization ledger account created", mzap.StorableRef(org), zap.String("account", sr.Account.Login))
|
||||
|
||||
roleDescription, err := a.pmanager.Role().Create(ctx, org.ID, &sr.OwnerRole)
|
||||
if err != nil {
|
||||
@@ -323,3 +328,57 @@ func (a *AccountAPI) openOrgWallet(ctx context.Context, org *model.Organization,
|
||||
a.logger.Info("Managed wallet created for organization", mzap.StorableRef(org), zap.String("wallet_ref", resp.Wallet.WalletRef))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *AccountAPI) openOrgLedgerAccount(ctx context.Context, org *model.Organization, sr *srequest.Signup) error {
|
||||
if a.ledgerClient == nil {
|
||||
a.logger.Warn("Ledger client not configured, skipping ledger account creation", mzap.StorableRef(org))
|
||||
return merrors.Internal("ledger client is not configured")
|
||||
}
|
||||
if a.chainAsset == nil {
|
||||
return merrors.Internal("chain gateway default asset is not configured")
|
||||
}
|
||||
|
||||
currency := strings.ToUpper(strings.TrimSpace(a.chainAsset.TokenSymbol))
|
||||
if currency == "" {
|
||||
return merrors.Internal("chain gateway default asset token symbol is not configured")
|
||||
}
|
||||
|
||||
var describable *describablev1.Describable
|
||||
name := strings.TrimSpace(sr.LedgerWallet.Name)
|
||||
var description *string
|
||||
if sr.LedgerWallet.Description != nil {
|
||||
trimmed := strings.TrimSpace(*sr.LedgerWallet.Description)
|
||||
if trimmed != "" {
|
||||
description = &trimmed
|
||||
}
|
||||
}
|
||||
if name != "" || description != nil {
|
||||
describable = &describablev1.Describable{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := a.ledgerClient.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: org.ID.Hex(),
|
||||
AccountType: ledgerv1.AccountType_ACCOUNT_TYPE_ASSET,
|
||||
Currency: currency,
|
||||
Status: ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE,
|
||||
Role: ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING,
|
||||
Metadata: map[string]string{
|
||||
"source": "signup",
|
||||
"login": sr.Account.Login,
|
||||
},
|
||||
Describable: describable,
|
||||
})
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create ledger account for organization", zap.Error(err), mzap.StorableRef(org))
|
||||
return err
|
||||
}
|
||||
if resp == nil || resp.GetAccount() == nil || strings.TrimSpace(resp.GetAccount().GetLedgerAccountRef()) == "" {
|
||||
return merrors.Internal("ledger returned empty account reference")
|
||||
}
|
||||
|
||||
a.logger.Info("Ledger account created for organization", mzap.StorableRef(org), zap.String("ledger_account_ref", resp.GetAccount().GetLedgerAccountRef()))
|
||||
return nil
|
||||
}
|
||||
|
||||
116
api/server/internal/server/accountapiimp/signup_ledger_test.go
Normal file
116
api/server/internal/server/accountapiimp/signup_ledger_test.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package accountapiimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
"github.com/tech/sendico/server/interface/api/srequest"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type stubLedgerAccountClient struct {
|
||||
createReq *ledgerv1.CreateAccountRequest
|
||||
createResp *ledgerv1.CreateAccountResponse
|
||||
createErr error
|
||||
}
|
||||
|
||||
func (s *stubLedgerAccountClient) CreateAccount(_ context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error) {
|
||||
s.createReq = req
|
||||
return s.createResp, s.createErr
|
||||
}
|
||||
|
||||
func (s *stubLedgerAccountClient) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestOpenOrgLedgerAccount(t *testing.T) {
|
||||
t.Run("creates operating ledger account", func(t *testing.T) {
|
||||
desc := " Main org ledger account "
|
||||
sr := &srequest.Signup{
|
||||
Account: model.AccountData{
|
||||
LoginData: model.LoginData{
|
||||
UserDataBase: model.UserDataBase{
|
||||
Login: "owner@example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
LedgerWallet: model.Describable{
|
||||
Name: " Primary Ledger ",
|
||||
Description: &desc,
|
||||
},
|
||||
}
|
||||
|
||||
org := &model.Organization{}
|
||||
org.SetID(bson.NewObjectID())
|
||||
|
||||
ledgerStub := &stubLedgerAccountClient{
|
||||
createResp: &ledgerv1.CreateAccountResponse{
|
||||
Account: &ledgerv1.LedgerAccount{LedgerAccountRef: bson.NewObjectID().Hex()},
|
||||
},
|
||||
}
|
||||
api := &AccountAPI{
|
||||
logger: zap.NewNop(),
|
||||
ledgerClient: ledgerStub,
|
||||
chainAsset: &chainv1.Asset{
|
||||
TokenSymbol: " usdt ",
|
||||
},
|
||||
}
|
||||
|
||||
err := api.openOrgLedgerAccount(context.Background(), org, sr)
|
||||
assert.NoError(t, err)
|
||||
if assert.NotNil(t, ledgerStub.createReq) {
|
||||
assert.Equal(t, org.ID.Hex(), ledgerStub.createReq.GetOrganizationRef())
|
||||
assert.Equal(t, "USDT", ledgerStub.createReq.GetCurrency())
|
||||
assert.Equal(t, ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, ledgerStub.createReq.GetAccountType())
|
||||
assert.Equal(t, ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, ledgerStub.createReq.GetStatus())
|
||||
assert.Equal(t, ledgerv1.AccountRole_ACCOUNT_ROLE_OPERATING, ledgerStub.createReq.GetRole())
|
||||
assert.Equal(t, map[string]string{
|
||||
"source": "signup",
|
||||
"login": "owner@example.com",
|
||||
}, ledgerStub.createReq.GetMetadata())
|
||||
if assert.NotNil(t, ledgerStub.createReq.GetDescribable()) {
|
||||
assert.Equal(t, "Primary Ledger", ledgerStub.createReq.GetDescribable().GetName())
|
||||
if assert.NotNil(t, ledgerStub.createReq.GetDescribable().Description) {
|
||||
assert.Equal(t, "Main org ledger account", ledgerStub.createReq.GetDescribable().GetDescription())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fails when ledger client is missing", func(t *testing.T) {
|
||||
api := &AccountAPI{
|
||||
logger: zap.NewNop(),
|
||||
chainAsset: &chainv1.Asset{
|
||||
TokenSymbol: "USDT",
|
||||
},
|
||||
}
|
||||
|
||||
err := api.openOrgLedgerAccount(context.Background(), &model.Organization{}, &srequest.Signup{})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, merrors.ErrInternal))
|
||||
})
|
||||
|
||||
t.Run("fails when ledger response has empty reference", func(t *testing.T) {
|
||||
ledgerStub := &stubLedgerAccountClient{
|
||||
createResp: &ledgerv1.CreateAccountResponse{},
|
||||
}
|
||||
api := &AccountAPI{
|
||||
logger: zap.NewNop(),
|
||||
ledgerClient: ledgerStub,
|
||||
chainAsset: &chainv1.Asset{
|
||||
TokenSymbol: "USDT",
|
||||
},
|
||||
}
|
||||
|
||||
err := api.openOrgLedgerAccount(context.Background(), &model.Organization{}, &srequest.Signup{})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, errors.Is(err, merrors.ErrInternal))
|
||||
})
|
||||
}
|
||||
@@ -2,45 +2,352 @@ package paymethodsimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/db/paymethod"
|
||||
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/internal/server/papitemplate"
|
||||
"go.uber.org/zap"
|
||||
"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 RecipientAPI struct {
|
||||
papitemplate.ProtectedAPI[model.PaymentMethod]
|
||||
db paymethod.DB
|
||||
type PaymentMethodsAPI struct {
|
||||
logger mlogger.Logger
|
||||
client methodsclient.Client
|
||||
oph mutil.ParamHelper
|
||||
rph mutil.ParamHelper
|
||||
mph mutil.ParamHelper
|
||||
}
|
||||
|
||||
func (a *RecipientAPI) Name() mservice.Type {
|
||||
func (a *PaymentMethodsAPI) Name() mservice.Type {
|
||||
return mservice.PaymentMethods
|
||||
}
|
||||
|
||||
func (a *RecipientAPI) Finish(_ context.Context) error {
|
||||
func (a *PaymentMethodsAPI) Finish(_ context.Context) error {
|
||||
if a.client != nil {
|
||||
return a.client.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateAPI(a eapi.API) (*RecipientAPI, error) {
|
||||
dbFactory := func() (papitemplate.ProtectedDB[model.PaymentMethod], error) {
|
||||
return a.DBFactory().NewPaymentMethodsDB()
|
||||
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")
|
||||
}
|
||||
|
||||
res := &RecipientAPI{}
|
||||
|
||||
p, err := papitemplate.CreateAPI(a, dbFactory, mservice.Recipients, mservice.PaymentMethods)
|
||||
address, err := resolveClientAddress("payment methods", cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.ProtectedAPI = *p.Build()
|
||||
|
||||
if res.db, err = a.DBFactory().NewPaymentMethodsDB(); err != nil {
|
||||
res.Logger.Warn("Failed to create payment methods database", zap.Error(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)
|
||||
}
|
||||
}
|
||||
|
||||
22
api/server/internal/server/recipientimp/notifications.go
Normal file
22
api/server/internal/server/recipientimp/notifications.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package recipientimp
|
||||
|
||||
import (
|
||||
messaging "github.com/tech/sendico/pkg/messaging/envelope"
|
||||
notifications "github.com/tech/sendico/pkg/messaging/notifications/object"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
nm "github.com/tech/sendico/pkg/model/notification"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
func (a *RecipientAPI) notification(
|
||||
recipient *model.Recipient,
|
||||
actorAccountRef bson.ObjectID,
|
||||
t nm.NotificationAction,
|
||||
) messaging.Envelope {
|
||||
objectRef := bson.NilObjectID
|
||||
if recipient != nil {
|
||||
objectRef = recipient.ID
|
||||
}
|
||||
|
||||
return notifications.Object(a.Name(), actorAccountRef, a.Name(), objectRef, t)
|
||||
}
|
||||
@@ -35,7 +35,11 @@ func CreateAPI(a eapi.API) (*RecipientAPI, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.ProtectedAPI = *p.Build()
|
||||
res.ProtectedAPI = *p.
|
||||
WithNotifications(res.notification).
|
||||
WithNoCreateNotification().
|
||||
WithNoUpdateNotification().
|
||||
Build()
|
||||
|
||||
if res.db, err = a.DBFactory().NewRecipientsDB(); err != nil {
|
||||
res.Logger.Warn("Failed to create recipients database", zap.Error(err))
|
||||
|
||||
Reference in New Issue
Block a user