Merge pull request '+ ledger account open endpoint' (#232) from ledger-231 into main
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
All checks were successful
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/discovery Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/gateway_chain Pipeline was successful
ci/woodpecker/push/gateway_mntx Pipeline was successful
ci/woodpecker/push/gateway_tgsettle Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
Reviewed-on: #232
This commit was merged in pull request #232.
This commit is contained in:
@@ -125,6 +125,8 @@ github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/i
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
|
||||
@@ -68,13 +68,13 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
||||
}
|
||||
|
||||
if strings.TrimSpace(reader.String("card_token")) != "" {
|
||||
resp, err := s.CreateCardTokenPayout(ctx, buildCardTokenPayoutRequest(reader, payoutID, amountMinor, currency))
|
||||
resp, err := s.CreateCardTokenPayout(ctx, buildCardTokenPayoutRequestFromParams(reader, payoutID, amountMinor, currency))
|
||||
if err != nil {
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
||||
}
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: payoutReceipt(resp.GetPayout())}, nil
|
||||
}
|
||||
resp, err := s.CreateCardPayout(ctx, buildCardPayoutRequest(reader, payoutID, amountMinor, currency))
|
||||
resp, err := s.CreateCardPayout(ctx, buildCardPayoutRequestFromParams(reader, payoutID, amountMinor, currency))
|
||||
if err != nil {
|
||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
||||
}
|
||||
@@ -160,7 +160,7 @@ func currencyFromOperation(op *connectorv1.Operation) string {
|
||||
return strings.ToUpper(currency)
|
||||
}
|
||||
|
||||
func buildCardTokenPayoutRequest(reader params.Reader, payoutID string, amountMinor int64, currency string) *mntxv1.CardTokenPayoutRequest {
|
||||
func buildCardTokenPayoutRequestFromParams(reader params.Reader, payoutID string, amountMinor int64, currency string) *mntxv1.CardTokenPayoutRequest {
|
||||
req := &mntxv1.CardTokenPayoutRequest{
|
||||
PayoutId: payoutID,
|
||||
ProjectId: readerInt64(reader, "project_id"),
|
||||
@@ -184,7 +184,7 @@ func buildCardTokenPayoutRequest(reader params.Reader, payoutID string, amountMi
|
||||
return req
|
||||
}
|
||||
|
||||
func buildCardPayoutRequest(reader params.Reader, payoutID string, amountMinor int64, currency string) *mntxv1.CardPayoutRequest {
|
||||
func buildCardPayoutRequestFromParams(reader params.Reader, payoutID string, amountMinor int64, currency string) *mntxv1.CardPayoutRequest {
|
||||
return &mntxv1.CardPayoutRequest{
|
||||
PayoutId: payoutID,
|
||||
ProjectId: readerInt64(reader, "project_id"),
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/tech/sendico/pkg/api/routers/gsresponse"
|
||||
gatewayv1 "github.com/tech/sendico/pkg/proto/common/gateway/v1"
|
||||
mntxv1 "github.com/tech/sendico/pkg/proto/gateway/mntx/v1"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// ListGatewayInstances exposes the Monetix gateway instance descriptors.
|
||||
@@ -25,16 +26,15 @@ func cloneGatewayDescriptor(src *gatewayv1.GatewayInstanceDescriptor) *gatewayv1
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
cp := *src
|
||||
cp := proto.Clone(src).(*gatewayv1.GatewayInstanceDescriptor)
|
||||
if src.Currencies != nil {
|
||||
cp.Currencies = append([]string(nil), src.Currencies...)
|
||||
}
|
||||
if src.Capabilities != nil {
|
||||
cap := *src.Capabilities
|
||||
cp.Capabilities = &cap
|
||||
cp.Capabilities = proto.Clone(src.Capabilities).(*gatewayv1.RailCapabilities)
|
||||
}
|
||||
if src.Limits != nil {
|
||||
limits := *src.Limits
|
||||
limits := &gatewayv1.Limits{}
|
||||
if src.Limits.VolumeLimit != nil {
|
||||
limits.VolumeLimit = map[string]string{}
|
||||
for key, value := range src.Limits.VolumeLimit {
|
||||
@@ -53,11 +53,10 @@ func cloneGatewayDescriptor(src *gatewayv1.GatewayInstanceDescriptor) *gatewayv1
|
||||
if value == nil {
|
||||
continue
|
||||
}
|
||||
clone := *value
|
||||
limits.CurrencyLimits[key] = &clone
|
||||
limits.CurrencyLimits[key] = proto.Clone(value).(*gatewayv1.LimitsOverride)
|
||||
}
|
||||
}
|
||||
cp.Limits = &limits
|
||||
cp.Limits = limits
|
||||
}
|
||||
return &cp
|
||||
return cp
|
||||
}
|
||||
|
||||
48
api/server/interface/api/srequest/ledger.go
Normal file
48
api/server/interface/api/srequest/ledger.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package srequest
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
)
|
||||
|
||||
type LedgerAccountType string
|
||||
|
||||
const (
|
||||
LedgerAccountTypeUnspecified LedgerAccountType = "unspecified"
|
||||
LedgerAccountTypeAsset LedgerAccountType = "asset"
|
||||
LedgerAccountTypeLiability LedgerAccountType = "liability"
|
||||
LedgerAccountTypeRevenue LedgerAccountType = "revenue"
|
||||
LedgerAccountTypeExpense LedgerAccountType = "expense"
|
||||
)
|
||||
|
||||
type LedgerAccountStatus string
|
||||
|
||||
const (
|
||||
LedgerAccountStatusUnspecified LedgerAccountStatus = "unspecified"
|
||||
LedgerAccountStatusActive LedgerAccountStatus = "active"
|
||||
LedgerAccountStatusFrozen LedgerAccountStatus = "frozen"
|
||||
)
|
||||
|
||||
type CreateLedgerAccount struct {
|
||||
AccountCode string `json:"accountCode"`
|
||||
AccountType LedgerAccountType `json:"accountType"`
|
||||
Currency string `json:"currency"`
|
||||
Status LedgerAccountStatus `json:"status,omitempty"`
|
||||
AllowNegative bool `json:"allowNegative,omitempty"`
|
||||
IsSettlement bool `json:"isSettlement,omitempty"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
func (r *CreateLedgerAccount) Validate() error {
|
||||
if strings.TrimSpace(r.AccountCode) == "" {
|
||||
return merrors.InvalidArgument("accountCode is required", "accountCode")
|
||||
}
|
||||
if strings.TrimSpace(r.Currency) == "" {
|
||||
return merrors.InvalidArgument("currency is required", "currency")
|
||||
}
|
||||
if strings.TrimSpace(string(r.AccountType)) == "" || strings.EqualFold(string(r.AccountType), string(LedgerAccountTypeUnspecified)) {
|
||||
return merrors.InvalidArgument("accountType is required", "accountType")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -29,6 +29,11 @@ type ledgerAccountsResponse struct {
|
||||
Accounts []ledgerAccount `json:"accounts"`
|
||||
}
|
||||
|
||||
type ledgerAccountResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Account ledgerAccount `json:"account"`
|
||||
}
|
||||
|
||||
type ledgerMoney struct {
|
||||
Amount string `json:"amount"`
|
||||
Currency string `json:"currency"`
|
||||
@@ -57,6 +62,13 @@ func LedgerAccounts(logger mlogger.Logger, accounts []*ledgerv1.LedgerAccount, a
|
||||
})
|
||||
}
|
||||
|
||||
func LedgerAccountCreated(logger mlogger.Logger, account *ledgerv1.LedgerAccount, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Created(logger, ledgerAccountResponse{
|
||||
Account: toLedgerAccount(account),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func LedgerBalance(logger mlogger.Logger, resp *ledgerv1.BalanceResponse, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, ledgerBalanceResponse{
|
||||
Balance: toLedgerBalance(resp),
|
||||
|
||||
122
api/server/internal/server/ledgerapiimp/create.go
Normal file
122
api/server/internal/server/ledgerapiimp/create.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package ledgerapiimp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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/mservice"
|
||||
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"
|
||||
mutil "github.com/tech/sendico/server/internal/mutil/param"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (a *LedgerAPI) createAccount(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 ledger account create", 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, primitive.NilObjectID, model.ActionCreate)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to check ledger accounts 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 creating ledger account", mutil.PLog(a.oph, r))
|
||||
return response.AccessDenied(a.logger, a.Name(), "ledger accounts write permission denied")
|
||||
}
|
||||
|
||||
payload, err := decodeLedgerAccountCreatePayload(r)
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to decode ledger account create payload", zap.Error(err), mutil.PLog(a.oph, r))
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
accountType, err := mapLedgerAccountType(payload.AccountType)
|
||||
if err != nil {
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
status, err := mapLedgerAccountStatus(payload.Status)
|
||||
if err != nil {
|
||||
return response.BadPayload(a.logger, a.Name(), err)
|
||||
}
|
||||
|
||||
if a.client == nil {
|
||||
return response.Internal(a.logger, mservice.Ledger, errors.New("ledger client is not configured"))
|
||||
}
|
||||
|
||||
resp, err := a.client.CreateAccount(ctx, &ledgerv1.CreateAccountRequest{
|
||||
OrganizationRef: orgRef.Hex(),
|
||||
AccountCode: payload.AccountCode,
|
||||
AccountType: accountType,
|
||||
Currency: payload.Currency,
|
||||
Status: status,
|
||||
AllowNegative: payload.AllowNegative,
|
||||
IsSettlement: payload.IsSettlement,
|
||||
Metadata: payload.Metadata,
|
||||
})
|
||||
if err != nil {
|
||||
a.logger.Warn("Failed to create ledger account", zap.Error(err), zap.String("organization_ref", orgRef.Hex()))
|
||||
return response.Auto(a.logger, mservice.Ledger, err)
|
||||
}
|
||||
|
||||
return sresponse.LedgerAccountCreated(a.logger, resp.GetAccount(), token)
|
||||
}
|
||||
|
||||
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
|
||||
defer r.Body.Close()
|
||||
|
||||
payload := &srequest.CreateLedgerAccount{}
|
||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||
return nil, merrors.InvalidArgument("invalid payload: " + err.Error())
|
||||
}
|
||||
payload.AccountCode = strings.TrimSpace(payload.AccountCode)
|
||||
payload.Currency = strings.ToUpper(strings.TrimSpace(payload.Currency))
|
||||
if len(payload.Metadata) == 0 {
|
||||
payload.Metadata = nil
|
||||
}
|
||||
if err := payload.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func mapLedgerAccountType(accountType srequest.LedgerAccountType) (ledgerv1.AccountType, error) {
|
||||
switch strings.ToUpper(strings.TrimSpace(string(accountType))) {
|
||||
case "ACCOUNT_TYPE_ASSET", "ASSET":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_ASSET, nil
|
||||
case "ACCOUNT_TYPE_LIABILITY", "LIABILITY":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_LIABILITY, nil
|
||||
case "ACCOUNT_TYPE_REVENUE", "REVENUE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_REVENUE, nil
|
||||
case "ACCOUNT_TYPE_EXPENSE", "EXPENSE":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_EXPENSE, nil
|
||||
case "", "ACCOUNT_TYPE_UNSPECIFIED", "UNSPECIFIED":
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("accountType is required", "accountType")
|
||||
default:
|
||||
return ledgerv1.AccountType_ACCOUNT_TYPE_UNSPECIFIED, merrors.InvalidArgument("unsupported accountType: "+string(accountType), "accountType")
|
||||
}
|
||||
}
|
||||
|
||||
func mapLedgerAccountStatus(status srequest.LedgerAccountStatus) (ledgerv1.AccountStatus, error) {
|
||||
switch strings.ToUpper(strings.TrimSpace(string(status))) {
|
||||
case "", "ACCOUNT_STATUS_UNSPECIFIED", "UNSPECIFIED":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, nil
|
||||
case "ACCOUNT_STATUS_ACTIVE", "ACTIVE":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_ACTIVE, nil
|
||||
case "ACCOUNT_STATUS_FROZEN", "FROZEN":
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_FROZEN, nil
|
||||
default:
|
||||
return ledgerv1.AccountStatus_ACCOUNT_STATUS_UNSPECIFIED, merrors.InvalidArgument("unsupported status: "+string(status), "status")
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
)
|
||||
|
||||
type ledgerClient interface {
|
||||
CreateAccount(ctx context.Context, req *ledgerv1.CreateAccountRequest) (*ledgerv1.CreateAccountResponse, error)
|
||||
ListAccounts(ctx context.Context, req *ledgerv1.ListAccountsRequest) (*ledgerv1.ListAccountsResponse, error)
|
||||
GetBalance(ctx context.Context, req *ledgerv1.GetBalanceRequest) (*ledgerv1.BalanceResponse, error)
|
||||
Close() error
|
||||
@@ -75,6 +76,7 @@ func CreateAPI(apiCtx eapi.API) (*LedgerAPI, error) {
|
||||
}
|
||||
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Get, p.listAccounts)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.oph.AddRef("/"), api.Post, p.createAccount)
|
||||
apiCtx.Register().AccountHandler(p.Name(), p.aph.AddRef(p.oph.AddRef("/"))+"/balance", api.Get, p.getBalance)
|
||||
|
||||
return p, nil
|
||||
|
||||
Reference in New Issue
Block a user