move api/server to api/edge/bff
This commit is contained in:
62
api/edge/bff/interface/api/sresponse/account.go
Normal file
62
api/edge/bff/interface/api/sresponse/account.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type accountData struct {
|
||||
model.AccountPublic `json:",inline"`
|
||||
IsAnonymous bool `json:"isAnonymous"`
|
||||
}
|
||||
|
||||
type accountResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Account accountData `json:"account"`
|
||||
}
|
||||
|
||||
func _createAccount(account *model.Account, isAnonymous bool) *accountData {
|
||||
return &accountData{
|
||||
AccountPublic: account.AccountPublic,
|
||||
IsAnonymous: isAnonymous,
|
||||
}
|
||||
}
|
||||
|
||||
func _toAccount(account *model.Account, orgRef bson.ObjectID) *accountData {
|
||||
return _createAccount(account, model.AccountIsAnonymous(&account.UserDataBase, orgRef))
|
||||
}
|
||||
|
||||
func Account(logger mlogger.Logger, account *model.Account, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(
|
||||
logger,
|
||||
&accountResponse{
|
||||
Account: *_createAccount(account, false),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
type accountsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Accounts []accountData `json:"accounts"`
|
||||
}
|
||||
|
||||
func Accounts(logger mlogger.Logger, accounts []model.Account, orgRef bson.ObjectID, accessToken *TokenData) http.HandlerFunc {
|
||||
// Convert each account to its public representation.
|
||||
publicAccounts := make([]accountData, len(accounts))
|
||||
for i, a := range accounts {
|
||||
publicAccounts[i] = *_toAccount(&a, orgRef)
|
||||
}
|
||||
|
||||
return response.Ok(
|
||||
logger,
|
||||
&accountsResponse{
|
||||
Accounts: publicAccounts,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
)
|
||||
}
|
||||
5
api/edge/bff/interface/api/sresponse/authresp.go
Normal file
5
api/edge/bff/interface/api/sresponse/authresp.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package sresponse
|
||||
|
||||
type authResponse struct {
|
||||
AccessToken TokenData `json:"accessToken"`
|
||||
}
|
||||
15
api/edge/bff/interface/api/sresponse/badpassword.go
Normal file
15
api/edge/bff/interface/api/sresponse/badpassword.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func BadRPassword(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
|
||||
logger.Info("Failed password validation check", zap.Error(err))
|
||||
return response.BadRequest(logger, source, "invalid_request", err.Error())
|
||||
}
|
||||
24
api/edge/bff/interface/api/sresponse/dzone.go
Normal file
24
api/edge/bff/interface/api/sresponse/dzone.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type dzoneResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
DZone model.DZone `json:"dzone"`
|
||||
}
|
||||
|
||||
func DZone(logger mlogger.Logger, dzone *model.DZone, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(
|
||||
logger,
|
||||
&dzoneResponse{
|
||||
DZone: *dzone,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
)
|
||||
}
|
||||
16
api/edge/bff/interface/api/sresponse/file.go
Normal file
16
api/edge/bff/interface/api/sresponse/file.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
)
|
||||
|
||||
type fileUpladed struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func FileUploaded(logger mlogger.Logger, url string) http.HandlerFunc {
|
||||
return response.Ok(logger, &fileUpladed{URL: url})
|
||||
}
|
||||
21
api/edge/bff/interface/api/sresponse/invitation.go
Normal file
21
api/edge/bff/interface/api/sresponse/invitation.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type invitationResp struct {
|
||||
Invitation model.PublicInvitation `json:"invitation"`
|
||||
}
|
||||
|
||||
func Invitation(logger mlogger.Logger, invitation *model.PublicInvitation) http.HandlerFunc {
|
||||
return response.Ok(logger, &invitationResp{Invitation: *invitation})
|
||||
}
|
||||
|
||||
func Invitations(logger mlogger.Logger, invitations []model.Invitation) http.HandlerFunc {
|
||||
return response.Ok(logger, invitations)
|
||||
}
|
||||
126
api/edge/bff/interface/api/sresponse/ledger.go
Normal file
126
api/edge/bff/interface/api/sresponse/ledger.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
ledgerv1 "github.com/tech/sendico/pkg/proto/ledger/v1"
|
||||
)
|
||||
|
||||
type ledgerAccount struct {
|
||||
model.Describable `bson:",inline" json:",inline"`
|
||||
LedgerAccountRef string `json:"ledgerAccountRef"`
|
||||
OrganizationRef string `json:"organizationRef"`
|
||||
OwnerRef string `json:"ownerRef,omitempty"`
|
||||
AccountCode string `json:"accountCode"`
|
||||
AccountType string `json:"accountType"`
|
||||
Currency string `json:"currency"`
|
||||
Status string `json:"status"`
|
||||
AllowNegative bool `json:"allowNegative"`
|
||||
Role string `json:"role"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type ledgerAccountsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type ledgerBalance struct {
|
||||
LedgerAccountRef string `json:"ledgerAccountRef"`
|
||||
Balance *ledgerMoney `json:"balance,omitempty"`
|
||||
Version int64 `json:"version"`
|
||||
LastUpdated time.Time `json:"lastUpdated,omitempty"`
|
||||
}
|
||||
|
||||
type ledgerBalanceResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Balance ledgerBalance `json:"balance"`
|
||||
}
|
||||
|
||||
func LedgerAccounts(logger mlogger.Logger, accounts []*ledgerv1.LedgerAccount, accessToken *TokenData) http.HandlerFunc {
|
||||
dto := make([]ledgerAccount, 0, len(accounts))
|
||||
for _, acc := range accounts {
|
||||
dto = append(dto, toLedgerAccount(acc))
|
||||
}
|
||||
return response.Ok(logger, ledgerAccountsResponse{
|
||||
Accounts: dto,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
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),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func toLedgerAccount(acc *ledgerv1.LedgerAccount) ledgerAccount {
|
||||
if acc == nil {
|
||||
return ledgerAccount{}
|
||||
}
|
||||
return ledgerAccount{
|
||||
Describable: model.Describable{
|
||||
Name: acc.GetDescribable().GetName(),
|
||||
Description: acc.GetDescribable().Description,
|
||||
},
|
||||
LedgerAccountRef: acc.GetLedgerAccountRef(),
|
||||
OrganizationRef: acc.GetOrganizationRef(),
|
||||
OwnerRef: acc.GetOwnerRef(),
|
||||
AccountCode: acc.GetAccountCode(),
|
||||
AccountType: acc.GetAccountType().String(),
|
||||
Currency: acc.GetCurrency(),
|
||||
Status: acc.GetStatus().String(),
|
||||
AllowNegative: acc.GetAllowNegative(),
|
||||
Role: acc.GetRole().String(),
|
||||
Metadata: acc.GetMetadata(),
|
||||
CreatedAt: acc.GetCreatedAt().AsTime(),
|
||||
UpdatedAt: acc.GetUpdatedAt().AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func toLedgerBalance(resp *ledgerv1.BalanceResponse) ledgerBalance {
|
||||
if resp == nil {
|
||||
return ledgerBalance{}
|
||||
}
|
||||
return ledgerBalance{
|
||||
LedgerAccountRef: resp.GetLedgerAccountRef(),
|
||||
Balance: toLedgerMoney(resp.GetBalance()),
|
||||
Version: resp.GetVersion(),
|
||||
LastUpdated: resp.GetLastUpdated().AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
func toLedgerMoney(m *moneyv1.Money) *ledgerMoney {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return &ledgerMoney{
|
||||
Amount: m.GetAmount(),
|
||||
Currency: m.GetCurrency(),
|
||||
}
|
||||
}
|
||||
27
api/edge/bff/interface/api/sresponse/login.go
Normal file
27
api/edge/bff/interface/api/sresponse/login.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type loginResponse struct {
|
||||
accountResponse
|
||||
RefreshToken TokenData `json:"refreshToken"`
|
||||
}
|
||||
|
||||
func Login(logger mlogger.Logger, account *model.Account, accessToken, refreshToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(
|
||||
logger,
|
||||
&loginResponse{
|
||||
accountResponse: accountResponse{
|
||||
Account: *_createAccount(account, false),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
RefreshToken: *refreshToken,
|
||||
},
|
||||
)
|
||||
}
|
||||
29
api/edge/bff/interface/api/sresponse/login_pending.go
Normal file
29
api/edge/bff/interface/api/sresponse/login_pending.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type pendingLoginResponse struct {
|
||||
Account accountResponse `json:"account"`
|
||||
PendingToken TokenData `json:"pendingToken"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, target string) http.HandlerFunc {
|
||||
return response.Accepted(
|
||||
logger,
|
||||
&pendingLoginResponse{
|
||||
Account: accountResponse{
|
||||
Account: *_createAccount(account, false),
|
||||
authResponse: authResponse{},
|
||||
},
|
||||
PendingToken: *pendingToken,
|
||||
Target: target,
|
||||
},
|
||||
)
|
||||
}
|
||||
16
api/edge/bff/interface/api/sresponse/money.go
Normal file
16
api/edge/bff/interface/api/sresponse/money.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
)
|
||||
|
||||
func toMoney(m *moneyv1.Money) *paymenttypes.Money {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return &paymenttypes.Money{
|
||||
Amount: m.GetAmount(),
|
||||
Currency: m.GetCurrency(),
|
||||
}
|
||||
}
|
||||
49
api/edge/bff/interface/api/sresponse/objects.go
Normal file
49
api/edge/bff/interface/api/sresponse/objects.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
)
|
||||
|
||||
type DynamicResponse[T any] struct {
|
||||
authResponse `json:",inline"`
|
||||
Items []T
|
||||
// FieldName is the JSON key to use for the items.
|
||||
FieldName string
|
||||
}
|
||||
|
||||
func (dr DynamicResponse[T]) MarshalJSON() ([]byte, error) {
|
||||
// Create a temporary map to hold the keys and values.
|
||||
m := map[string]any{
|
||||
dr.FieldName: dr.Items,
|
||||
"accessToken": dr.AccessToken,
|
||||
}
|
||||
return json.Marshal(m)
|
||||
}
|
||||
|
||||
type handler = func(logger mlogger.Logger, data any) http.HandlerFunc
|
||||
|
||||
func objectsAuth[T any](logger mlogger.Logger, items []T, accessToken *TokenData, resource mservice.Type, handler handler) http.HandlerFunc {
|
||||
resp := &DynamicResponse[T]{
|
||||
Items: items,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
FieldName: resource,
|
||||
}
|
||||
return handler(logger, resp)
|
||||
}
|
||||
|
||||
func ObjectsAuth[T any](logger mlogger.Logger, items []T, accessToken *TokenData, resource mservice.Type) http.HandlerFunc {
|
||||
return objectsAuth(logger, items, accessToken, resource, response.Ok)
|
||||
}
|
||||
|
||||
func ObjectAuth[T any](logger mlogger.Logger, item *T, accessToken *TokenData, resource mservice.Type) http.HandlerFunc {
|
||||
return ObjectsAuth(logger, []T{*item}, accessToken, resource)
|
||||
}
|
||||
|
||||
func ObjectAuthCreated[T any](logger mlogger.Logger, item *T, accessToken *TokenData, resource mservice.Type) http.HandlerFunc {
|
||||
return objectsAuth(logger, []T{*item}, accessToken, resource, response.Created)
|
||||
}
|
||||
35
api/edge/bff/interface/api/sresponse/orgnization.go
Normal file
35
api/edge/bff/interface/api/sresponse/orgnization.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type organizationsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Organizations []model.Organization `json:"organizations"`
|
||||
}
|
||||
|
||||
func Organization(logger mlogger.Logger, organization *model.Organization, accessToken *TokenData) http.HandlerFunc {
|
||||
return Organizations(logger, []model.Organization{*organization}, accessToken)
|
||||
}
|
||||
|
||||
func Organizations(logger mlogger.Logger, organizations []model.Organization, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, organizationsResponse{
|
||||
Organizations: organizations,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
type organizationPublicResponse struct {
|
||||
Organizations []model.OrganizationBase `json:"organizations"`
|
||||
}
|
||||
|
||||
func OrganizationPublic(logger mlogger.Logger, organization *model.OrganizationBase) http.HandlerFunc {
|
||||
return response.Ok(logger, organizationPublicResponse{
|
||||
[]model.OrganizationBase{*organization},
|
||||
})
|
||||
}
|
||||
389
api/edge/bff/interface/api/sresponse/payment.go
Normal file
389
api/edge/bff/interface/api/sresponse/payment.go
Normal file
@@ -0,0 +1,389 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
|
||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||
oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
|
||||
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type FeeLine struct {
|
||||
LedgerAccountRef string `json:"ledgerAccountRef,omitempty"`
|
||||
Amount *paymenttypes.Money `json:"amount,omitempty"`
|
||||
LineType string `json:"lineType,omitempty"`
|
||||
Side string `json:"side,omitempty"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type FxQuote struct {
|
||||
QuoteRef string `json:"quoteRef,omitempty"`
|
||||
BaseCurrency string `json:"baseCurrency,omitempty"`
|
||||
QuoteCurrency string `json:"quoteCurrency,omitempty"`
|
||||
Side string `json:"side,omitempty"`
|
||||
Price string `json:"price,omitempty"`
|
||||
BaseAmount *paymenttypes.Money `json:"baseAmount,omitempty"`
|
||||
QuoteAmount *paymenttypes.Money `json:"quoteAmount,omitempty"`
|
||||
ExpiresAtUnixMs int64 `json:"expiresAtUnixMs,omitempty"`
|
||||
PricedAtUnixMs int64 `json:"pricedAtUnixMs,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
RateRef string `json:"rateRef,omitempty"`
|
||||
Firm bool `json:"firm,omitempty"`
|
||||
}
|
||||
|
||||
type PaymentQuote struct {
|
||||
QuoteRef string `json:"quoteRef,omitempty"`
|
||||
IntentRef string `json:"intentRef,omitempty"`
|
||||
Amounts *QuoteAmounts `json:"amounts,omitempty"`
|
||||
Fees *QuoteFees `json:"fees,omitempty"`
|
||||
FxQuote *FxQuote `json:"fxQuote,omitempty"`
|
||||
}
|
||||
|
||||
type QuoteAmounts struct {
|
||||
SourcePrincipal *paymenttypes.Money `json:"sourcePrincipal,omitempty"`
|
||||
SourceDebitTotal *paymenttypes.Money `json:"sourceDebitTotal,omitempty"`
|
||||
DestinationSettlement *paymenttypes.Money `json:"destinationSettlement,omitempty"`
|
||||
}
|
||||
|
||||
type QuoteFees struct {
|
||||
Lines []FeeLine `json:"lines,omitempty"`
|
||||
}
|
||||
|
||||
type PaymentQuotes struct {
|
||||
IdempotencyKey string `json:"idempotencyKey,omitempty"`
|
||||
QuoteRef string `json:"quoteRef,omitempty"`
|
||||
Items []PaymentQuote `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type Payment struct {
|
||||
PaymentRef string `json:"paymentRef,omitempty"`
|
||||
IdempotencyKey string `json:"idempotencyKey,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
FailureCode string `json:"failureCode,omitempty"`
|
||||
FailureReason string `json:"failureReason,omitempty"`
|
||||
Operations []PaymentOperation `json:"operations,omitempty"`
|
||||
LastQuote *PaymentQuote `json:"lastQuote,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
Meta map[string]string `json:"meta,omitempty"`
|
||||
}
|
||||
|
||||
type PaymentOperation struct {
|
||||
StepRef string `json:"stepRef,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
FailureCode string `json:"failureCode,omitempty"`
|
||||
FailureReason string `json:"failureReason,omitempty"`
|
||||
StartedAt time.Time `json:"startedAt,omitempty"`
|
||||
CompletedAt time.Time `json:"completedAt,omitempty"`
|
||||
}
|
||||
|
||||
type paymentQuoteResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
IdempotencyKey string `json:"idempotencyKey,omitempty"`
|
||||
Quote *PaymentQuote `json:"quote"`
|
||||
}
|
||||
|
||||
type paymentQuotesResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Quote *PaymentQuotes `json:"quote"`
|
||||
}
|
||||
|
||||
type paymentsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Payments []Payment `json:"payments"`
|
||||
Page *paginationv1.CursorPageResponse `json:"page,omitempty"`
|
||||
}
|
||||
|
||||
type paymentResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Payment *Payment `json:"payment"`
|
||||
}
|
||||
|
||||
// PaymentQuote wraps a payment quote with refreshed access token.
|
||||
func PaymentQuoteResponse(logger mlogger.Logger, idempotencyKey string, quote *quotationv2.PaymentQuote, token *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, paymentQuoteResponse{
|
||||
Quote: toPaymentQuote(quote),
|
||||
IdempotencyKey: idempotencyKey,
|
||||
authResponse: authResponse{AccessToken: *token},
|
||||
})
|
||||
}
|
||||
|
||||
// PaymentQuotes wraps batch quotes with refreshed access token.
|
||||
func PaymentQuotesResponse(logger mlogger.Logger, resp *quotationv2.QuotePaymentsResponse, token *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, paymentQuotesResponse{
|
||||
Quote: toPaymentQuotes(resp),
|
||||
authResponse: authResponse{AccessToken: *token},
|
||||
})
|
||||
}
|
||||
|
||||
// Payments wraps a list of payments with refreshed access token.
|
||||
func PaymentsResponse(logger mlogger.Logger, payments []*orchestrationv2.Payment, token *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, paymentsResponse{
|
||||
Payments: toPayments(payments),
|
||||
authResponse: authResponse{AccessToken: *token},
|
||||
})
|
||||
}
|
||||
|
||||
// PaymentsList wraps a list of payments with refreshed access token and pagination data.
|
||||
func PaymentsListResponse(logger mlogger.Logger, resp *orchestrationv2.ListPaymentsResponse, token *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, paymentsResponse{
|
||||
Payments: toPayments(resp.GetPayments()),
|
||||
Page: resp.GetPage(),
|
||||
authResponse: authResponse{AccessToken: *token},
|
||||
})
|
||||
}
|
||||
|
||||
// Payment wraps a payment with refreshed access token.
|
||||
func PaymentResponse(logger mlogger.Logger, payment *orchestrationv2.Payment, token *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, paymentResponse{
|
||||
Payment: toPayment(payment),
|
||||
authResponse: authResponse{AccessToken: *token},
|
||||
})
|
||||
}
|
||||
|
||||
func toFeeLines(lines []*feesv1.DerivedPostingLine) []FeeLine {
|
||||
if len(lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]FeeLine, 0, len(lines))
|
||||
for _, line := range lines {
|
||||
if line == nil {
|
||||
continue
|
||||
}
|
||||
result = append(result, FeeLine{
|
||||
LedgerAccountRef: line.GetLedgerAccountRef(),
|
||||
Amount: toMoney(line.GetMoney()),
|
||||
LineType: enumJSONName(line.GetLineType().String()),
|
||||
Side: enumJSONName(line.GetSide().String()),
|
||||
Meta: line.GetMeta(),
|
||||
})
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toFxQuote(q *oraclev1.Quote) *FxQuote {
|
||||
if q == nil {
|
||||
return nil
|
||||
}
|
||||
pair := q.GetPair()
|
||||
pricedAtUnixMs := int64(0)
|
||||
if ts := q.GetPricedAt(); ts != nil {
|
||||
pricedAtUnixMs = ts.AsTime().UnixMilli()
|
||||
}
|
||||
base := ""
|
||||
quote := ""
|
||||
if pair != nil {
|
||||
base = pair.GetBase()
|
||||
quote = pair.GetQuote()
|
||||
}
|
||||
return &FxQuote{
|
||||
QuoteRef: q.GetQuoteRef(),
|
||||
BaseCurrency: base,
|
||||
QuoteCurrency: quote,
|
||||
Side: enumJSONName(q.GetSide().String()),
|
||||
Price: q.GetPrice().GetValue(),
|
||||
BaseAmount: toMoney(q.GetBaseAmount()),
|
||||
QuoteAmount: toMoney(q.GetQuoteAmount()),
|
||||
ExpiresAtUnixMs: q.GetExpiresAtUnixMs(),
|
||||
PricedAtUnixMs: pricedAtUnixMs,
|
||||
Provider: q.GetProvider(),
|
||||
RateRef: q.GetRateRef(),
|
||||
Firm: q.GetFirm(),
|
||||
}
|
||||
}
|
||||
|
||||
func toPaymentQuote(q *quotationv2.PaymentQuote) *PaymentQuote {
|
||||
if q == nil {
|
||||
return nil
|
||||
}
|
||||
amounts := toQuoteAmounts(q)
|
||||
fees := toQuoteFees(q.GetFeeLines())
|
||||
return &PaymentQuote{
|
||||
QuoteRef: q.GetQuoteRef(),
|
||||
IntentRef: strings.TrimSpace(q.GetIntentRef()),
|
||||
Amounts: amounts,
|
||||
Fees: fees,
|
||||
FxQuote: toFxQuote(q.GetFxQuote()),
|
||||
}
|
||||
}
|
||||
|
||||
func toPaymentQuotes(resp *quotationv2.QuotePaymentsResponse) *PaymentQuotes {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
items := make([]PaymentQuote, 0, len(resp.GetQuotes()))
|
||||
for _, quote := range resp.GetQuotes() {
|
||||
if dto := toPaymentQuote(quote); dto != nil {
|
||||
items = append(items, *dto)
|
||||
}
|
||||
}
|
||||
if len(items) == 0 {
|
||||
items = nil
|
||||
}
|
||||
return &PaymentQuotes{
|
||||
IdempotencyKey: resp.GetIdempotencyKey(),
|
||||
QuoteRef: resp.GetQuoteRef(),
|
||||
Items: items,
|
||||
}
|
||||
}
|
||||
|
||||
func toQuoteAmounts(q *quotationv2.PaymentQuote) *QuoteAmounts {
|
||||
if q == nil {
|
||||
return nil
|
||||
}
|
||||
amounts := &QuoteAmounts{
|
||||
SourcePrincipal: toMoney(q.GetTransferPrincipalAmount()),
|
||||
SourceDebitTotal: toMoney(q.GetPayerTotalDebitAmount()),
|
||||
DestinationSettlement: toMoney(q.GetDestinationAmount()),
|
||||
}
|
||||
if amounts.SourcePrincipal == nil && amounts.SourceDebitTotal == nil && amounts.DestinationSettlement == nil {
|
||||
return nil
|
||||
}
|
||||
return amounts
|
||||
}
|
||||
|
||||
func toQuoteFees(lines []*feesv1.DerivedPostingLine) *QuoteFees {
|
||||
feeLines := toFeeLines(lines)
|
||||
if len(feeLines) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &QuoteFees{Lines: feeLines}
|
||||
}
|
||||
|
||||
func toPayments(items []*orchestrationv2.Payment) []Payment {
|
||||
if len(items) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]Payment, 0, len(items))
|
||||
for _, item := range items {
|
||||
if p := toPayment(item); p != nil {
|
||||
result = append(result, *p)
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toPayment(p *orchestrationv2.Payment) *Payment {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
operations := toUserVisibleOperations(p.GetStepExecutions())
|
||||
failureCode, failureReason := firstFailure(operations)
|
||||
return &Payment{
|
||||
PaymentRef: p.GetPaymentRef(),
|
||||
State: enumJSONName(p.GetState().String()),
|
||||
FailureCode: failureCode,
|
||||
FailureReason: failureReason,
|
||||
Operations: operations,
|
||||
LastQuote: toPaymentQuote(p.GetQuoteSnapshot()),
|
||||
CreatedAt: timestampAsTime(p.GetCreatedAt()),
|
||||
Meta: paymentMeta(p),
|
||||
IdempotencyKey: "",
|
||||
}
|
||||
}
|
||||
|
||||
func firstFailure(operations []PaymentOperation) (string, string) {
|
||||
for _, op := range operations {
|
||||
if strings.TrimSpace(op.FailureCode) == "" && strings.TrimSpace(op.FailureReason) == "" {
|
||||
continue
|
||||
}
|
||||
return strings.TrimSpace(op.FailureCode), strings.TrimSpace(op.FailureReason)
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func toUserVisibleOperations(steps []*orchestrationv2.StepExecution) []PaymentOperation {
|
||||
if len(steps) == 0 {
|
||||
return nil
|
||||
}
|
||||
ops := make([]PaymentOperation, 0, len(steps))
|
||||
for _, step := range steps {
|
||||
if step == nil || !isUserVisibleStep(step.GetReportVisibility()) {
|
||||
continue
|
||||
}
|
||||
ops = append(ops, toPaymentOperation(step))
|
||||
}
|
||||
if len(ops) == 0 {
|
||||
return nil
|
||||
}
|
||||
return ops
|
||||
}
|
||||
|
||||
func toPaymentOperation(step *orchestrationv2.StepExecution) PaymentOperation {
|
||||
op := PaymentOperation{
|
||||
StepRef: step.GetStepRef(),
|
||||
Code: step.GetStepCode(),
|
||||
State: enumJSONName(step.GetState().String()),
|
||||
Label: strings.TrimSpace(step.GetUserLabel()),
|
||||
StartedAt: timestampAsTime(step.GetStartedAt()),
|
||||
CompletedAt: timestampAsTime(step.GetCompletedAt()),
|
||||
}
|
||||
failure := step.GetFailure()
|
||||
if failure == nil {
|
||||
return op
|
||||
}
|
||||
op.FailureCode = enumJSONName(failure.GetCategory().String())
|
||||
op.FailureReason = strings.TrimSpace(failure.GetMessage())
|
||||
if op.FailureReason == "" {
|
||||
op.FailureReason = strings.TrimSpace(failure.GetCode())
|
||||
}
|
||||
return op
|
||||
}
|
||||
|
||||
func isUserVisibleStep(visibility orchestrationv2.ReportVisibility) bool {
|
||||
switch visibility {
|
||||
case orchestrationv2.ReportVisibility_REPORT_VISIBILITY_HIDDEN,
|
||||
orchestrationv2.ReportVisibility_REPORT_VISIBILITY_BACKOFFICE,
|
||||
orchestrationv2.ReportVisibility_REPORT_VISIBILITY_AUDIT:
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func paymentMeta(p *orchestrationv2.Payment) map[string]string {
|
||||
if p == nil {
|
||||
return nil
|
||||
}
|
||||
meta := make(map[string]string)
|
||||
if quotationRef := strings.TrimSpace(p.GetQuotationRef()); quotationRef != "" {
|
||||
meta["quotationRef"] = quotationRef
|
||||
}
|
||||
if clientPaymentRef := strings.TrimSpace(p.GetClientPaymentRef()); clientPaymentRef != "" {
|
||||
meta["clientPaymentRef"] = clientPaymentRef
|
||||
}
|
||||
if version := p.GetVersion(); version > 0 {
|
||||
meta["version"] = strconv.FormatUint(version, 10)
|
||||
}
|
||||
if len(meta) == 0 {
|
||||
return nil
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
func timestampAsTime(ts *timestamppb.Timestamp) time.Time {
|
||||
if ts == nil {
|
||||
return time.Time{}
|
||||
}
|
||||
return ts.AsTime()
|
||||
}
|
||||
|
||||
func enumJSONName(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
136
api/edge/bff/interface/api/sresponse/payment_test.go
Normal file
136
api/edge/bff/interface/api/sresponse/payment_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||
quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
|
||||
sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
|
||||
)
|
||||
|
||||
func TestToUserVisibleOperationsFiltersByVisibility(t *testing.T) {
|
||||
steps := []*orchestrationv2.StepExecution{
|
||||
{
|
||||
StepRef: "hidden",
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_HIDDEN,
|
||||
},
|
||||
{
|
||||
StepRef: "user",
|
||||
StepCode: "hop.4.card_payout.send",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_RUNNING,
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_USER,
|
||||
},
|
||||
{
|
||||
StepRef: "unspecified",
|
||||
StepCode: "hop.4.card_payout.observe",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_COMPLETED,
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
StepRef: "backoffice",
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_BACKOFFICE,
|
||||
},
|
||||
}
|
||||
|
||||
ops := toUserVisibleOperations(steps)
|
||||
if len(ops) != 2 {
|
||||
t.Fatalf("operations count mismatch: got=%d want=2", len(ops))
|
||||
}
|
||||
if got, want := ops[0].StepRef, "user"; got != want {
|
||||
t.Fatalf("first operation step_ref mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := ops[1].StepRef, "unspecified"; got != want {
|
||||
t.Fatalf("second operation step_ref mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPaymentFailureUsesVisibleOperationsOnly(t *testing.T) {
|
||||
dto := toPayment(&orchestrationv2.Payment{
|
||||
PaymentRef: "pay-1",
|
||||
State: orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_FAILED,
|
||||
StepExecutions: []*orchestrationv2.StepExecution{
|
||||
{
|
||||
StepRef: "hidden_failed",
|
||||
StepCode: "edge.1_2.ledger.debit",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_FAILED,
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_HIDDEN,
|
||||
Failure: &orchestrationv2.Failure{
|
||||
Category: sharedv1.PaymentFailureCode_FAILURE_LEDGER,
|
||||
Message: "internal hold release failure",
|
||||
},
|
||||
},
|
||||
{
|
||||
StepRef: "user_failed",
|
||||
StepCode: "hop.4.card_payout.send",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_FAILED,
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_USER,
|
||||
Failure: &orchestrationv2.Failure{
|
||||
Category: sharedv1.PaymentFailureCode_FAILURE_CHAIN,
|
||||
Message: "card declined",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if dto == nil {
|
||||
t.Fatal("expected non-nil payment dto")
|
||||
}
|
||||
if got, want := dto.FailureCode, "failure_chain"; got != want {
|
||||
t.Fatalf("failure_code mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := dto.FailureReason, "card declined"; got != want {
|
||||
t.Fatalf("failure_reason mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if len(dto.Operations) != 1 {
|
||||
t.Fatalf("operations count mismatch: got=%d want=1", len(dto.Operations))
|
||||
}
|
||||
if got, want := dto.Operations[0].StepRef, "user_failed"; got != want {
|
||||
t.Fatalf("visible operation mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPaymentIgnoresHiddenFailures(t *testing.T) {
|
||||
dto := toPayment(&orchestrationv2.Payment{
|
||||
PaymentRef: "pay-2",
|
||||
State: orchestrationv2.OrchestrationState_ORCHESTRATION_STATE_FAILED,
|
||||
StepExecutions: []*orchestrationv2.StepExecution{
|
||||
{
|
||||
StepRef: "hidden_failed",
|
||||
StepCode: "edge.1_2.ledger.release",
|
||||
State: orchestrationv2.StepExecutionState_STEP_EXECUTION_STATE_FAILED,
|
||||
ReportVisibility: orchestrationv2.ReportVisibility_REPORT_VISIBILITY_BACKOFFICE,
|
||||
Failure: &orchestrationv2.Failure{
|
||||
Category: sharedv1.PaymentFailureCode_FAILURE_LEDGER,
|
||||
Message: "backoffice only failure",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if dto == nil {
|
||||
t.Fatal("expected non-nil payment dto")
|
||||
}
|
||||
if got := dto.FailureCode; got != "" {
|
||||
t.Fatalf("expected empty failure_code, got=%q", got)
|
||||
}
|
||||
if got := dto.FailureReason; got != "" {
|
||||
t.Fatalf("expected empty failure_reason, got=%q", got)
|
||||
}
|
||||
if len(dto.Operations) != 0 {
|
||||
t.Fatalf("expected no visible operations, got=%d", len(dto.Operations))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToPaymentQuote_MapsIntentRef(t *testing.T) {
|
||||
dto := toPaymentQuote("ationv2.PaymentQuote{
|
||||
QuoteRef: "quote-1",
|
||||
IntentRef: "intent-1",
|
||||
})
|
||||
if dto == nil {
|
||||
t.Fatal("expected non-nil quote dto")
|
||||
}
|
||||
if got, want := dto.QuoteRef, "quote-1"; got != want {
|
||||
t.Fatalf("quote_ref mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
if got, want := dto.IntentRef, "intent-1"; got != want {
|
||||
t.Fatalf("intent_ref mismatch: got=%q want=%q", got, want)
|
||||
}
|
||||
}
|
||||
45
api/edge/bff/interface/api/sresponse/permissions.go
Normal file
45
api/edge/bff/interface/api/sresponse/permissions.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type permissionsDescription struct {
|
||||
Roles []model.RoleDescription `json:"roles"`
|
||||
Policies []model.PolicyDescription `json:"policies"`
|
||||
}
|
||||
|
||||
type permissionsData struct {
|
||||
Roles []model.Role `json:"roles"`
|
||||
Policies []model.RolePolicy `json:"policies"`
|
||||
Permissions []model.Permission `json:"permissions"`
|
||||
}
|
||||
|
||||
type permissionsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Descriptions permissionsDescription `json:"descriptions"`
|
||||
Permissions permissionsData `json:"permissions"`
|
||||
}
|
||||
|
||||
func Permisssions(logger mlogger.Logger,
|
||||
rolesDescs []model.RoleDescription, policiesDescs []model.PolicyDescription,
|
||||
roles []model.Role, policies []model.RolePolicy, permissions []model.Permission,
|
||||
accessToken *TokenData,
|
||||
) http.HandlerFunc {
|
||||
return response.Ok(logger, permissionsResponse{
|
||||
Descriptions: permissionsDescription{
|
||||
Roles: rolesDescs,
|
||||
Policies: policiesDescs,
|
||||
},
|
||||
Permissions: permissionsData{
|
||||
Roles: roles,
|
||||
Policies: policies,
|
||||
Permissions: permissions,
|
||||
},
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
14
api/edge/bff/interface/api/sresponse/response.go
Normal file
14
api/edge/bff/interface/api/sresponse/response.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
emodel "github.com/tech/sendico/server/interface/model"
|
||||
)
|
||||
|
||||
type (
|
||||
HandlerFunc = func(r *http.Request) http.HandlerFunc
|
||||
AccountHandlerFunc = func(r *http.Request, account *model.Account, accessToken *TokenData) http.HandlerFunc
|
||||
PendingAccountHandlerFunc = func(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc
|
||||
)
|
||||
27
api/edge/bff/interface/api/sresponse/result.go
Normal file
27
api/edge/bff/interface/api/sresponse/result.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
)
|
||||
|
||||
type resultAuth struct {
|
||||
authResponse `json:",inline"`
|
||||
response.Result `json:",inline"`
|
||||
}
|
||||
|
||||
func Success(logger mlogger.Logger, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, &resultAuth{
|
||||
Result: response.Result{Result: true},
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func Failed(logger mlogger.Logger, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Accepted(logger, &resultAuth{
|
||||
Result: response.Result{Result: false},
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
16
api/edge/bff/interface/api/sresponse/signup.go
Normal file
16
api/edge/bff/interface/api/sresponse/signup.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
func SignUp(logger mlogger.Logger, account *model.Account) http.HandlerFunc {
|
||||
return response.Ok(
|
||||
logger,
|
||||
&account.AccountBase,
|
||||
)
|
||||
}
|
||||
23
api/edge/bff/interface/api/sresponse/signupavailability.go
Normal file
23
api/edge/bff/interface/api/sresponse/signupavailability.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
)
|
||||
|
||||
type SignupAvailability struct {
|
||||
Login string `json:"login"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
func SignUpAvailability(logger mlogger.Logger, login string, available bool) http.HandlerFunc {
|
||||
return response.Ok(
|
||||
logger,
|
||||
SignupAvailability{
|
||||
Login: login,
|
||||
Available: available,
|
||||
},
|
||||
)
|
||||
}
|
||||
8
api/edge/bff/interface/api/sresponse/token.go
Normal file
8
api/edge/bff/interface/api/sresponse/token.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package sresponse
|
||||
|
||||
import "time"
|
||||
|
||||
type TokenData struct {
|
||||
Token string `json:"token"`
|
||||
Expiration time.Time `json:"expiration"`
|
||||
}
|
||||
292
api/edge/bff/interface/api/sresponse/wallet.go
Normal file
292
api/edge/bff/interface/api/sresponse/wallet.go
Normal file
@@ -0,0 +1,292 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
paymenttypes "github.com/tech/sendico/pkg/payments/types"
|
||||
moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
|
||||
paginationv1 "github.com/tech/sendico/pkg/proto/common/pagination/v1"
|
||||
connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
|
||||
chainv1 "github.com/tech/sendico/pkg/proto/gateway/chain/v1"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type walletAsset struct {
|
||||
Chain string `json:"chain"`
|
||||
TokenSymbol string `json:"tokenSymbol"`
|
||||
ContractAddress string `json:"contractAddress"`
|
||||
}
|
||||
|
||||
type wallet struct {
|
||||
WalletRef string `json:"walletRef"`
|
||||
OrganizationRef string `json:"organizationRef"`
|
||||
OwnerRef string `json:"ownerRef"`
|
||||
Asset walletAsset `json:"asset"`
|
||||
DepositAddress string `json:"depositAddress"`
|
||||
Status string `json:"status"`
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
CreatedAt string `json:"createdAt,omitempty"`
|
||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type walletsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Wallets []wallet `json:"wallets"`
|
||||
Page *paginationv1.CursorPageResponse `json:"page,omitempty"`
|
||||
}
|
||||
|
||||
type walletBalance struct {
|
||||
Available *paymenttypes.Money `json:"available,omitempty"`
|
||||
PendingInbound *paymenttypes.Money `json:"pendingInbound,omitempty"`
|
||||
PendingOutbound *paymenttypes.Money `json:"pendingOutbound,omitempty"`
|
||||
CalculatedAt string `json:"calculatedAt,omitempty"`
|
||||
}
|
||||
|
||||
type walletBalanceResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Balance walletBalance `json:"balance"`
|
||||
}
|
||||
|
||||
func Wallets(logger mlogger.Logger, resp *chainv1.ListManagedWalletsResponse, accessToken *TokenData) http.HandlerFunc {
|
||||
dto := walletsResponse{
|
||||
Page: resp.GetPage(),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
}
|
||||
dto.Wallets = make([]wallet, 0, len(resp.GetWallets()))
|
||||
for _, w := range resp.GetWallets() {
|
||||
dto.Wallets = append(dto.Wallets, toWallet(w))
|
||||
}
|
||||
return response.Ok(logger, dto)
|
||||
}
|
||||
|
||||
func WalletBalance(logger mlogger.Logger, bal *chainv1.WalletBalance, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, walletBalanceResponse{
|
||||
Balance: toWalletBalance(bal),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func toWallet(w *chainv1.ManagedWallet) wallet {
|
||||
if w == nil {
|
||||
return wallet{}
|
||||
}
|
||||
asset := w.GetAsset()
|
||||
chain := ""
|
||||
token := ""
|
||||
contract := ""
|
||||
if asset != nil {
|
||||
chain = chainNetworkValue(asset.GetChain())
|
||||
token = asset.GetTokenSymbol()
|
||||
contract = asset.GetContractAddress()
|
||||
}
|
||||
name := ""
|
||||
if d := w.GetDescribable(); d != nil {
|
||||
name = strings.TrimSpace(d.GetName())
|
||||
}
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(w.GetMetadata()["name"])
|
||||
}
|
||||
if name == "" {
|
||||
name = w.GetWalletRef()
|
||||
}
|
||||
var description *string
|
||||
if d := w.GetDescribable(); d != nil && d.Description != nil {
|
||||
if trimmed := strings.TrimSpace(d.GetDescription()); trimmed != "" {
|
||||
description = &trimmed
|
||||
}
|
||||
}
|
||||
if description == nil {
|
||||
if trimmed := strings.TrimSpace(w.GetMetadata()["description"]); trimmed != "" {
|
||||
description = &trimmed
|
||||
}
|
||||
}
|
||||
return wallet{
|
||||
WalletRef: w.GetWalletRef(),
|
||||
OrganizationRef: w.GetOrganizationRef(),
|
||||
OwnerRef: w.GetOwnerRef(),
|
||||
Asset: walletAsset{
|
||||
Chain: chain,
|
||||
TokenSymbol: token,
|
||||
ContractAddress: contract,
|
||||
},
|
||||
DepositAddress: w.GetDepositAddress(),
|
||||
Status: w.GetStatus().String(),
|
||||
Metadata: w.GetMetadata(),
|
||||
Name: name,
|
||||
Description: description,
|
||||
CreatedAt: tsToString(w.GetCreatedAt()),
|
||||
UpdatedAt: tsToString(w.GetUpdatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func toWalletBalance(b *chainv1.WalletBalance) walletBalance {
|
||||
if b == nil {
|
||||
return walletBalance{}
|
||||
}
|
||||
return walletBalance{
|
||||
Available: toMoney(b.GetAvailable()),
|
||||
PendingInbound: toMoney(b.GetPendingInbound()),
|
||||
PendingOutbound: toMoney(b.GetPendingOutbound()),
|
||||
CalculatedAt: tsToString(b.GetCalculatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func tsToString(ts *timestamppb.Timestamp) string {
|
||||
if ts == nil {
|
||||
return ""
|
||||
}
|
||||
return ts.AsTime().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func chainNetworkValue(chain chainv1.ChainNetwork) string {
|
||||
name := chain.String()
|
||||
if !strings.HasPrefix(name, "CHAIN_NETWORK_") {
|
||||
return "unspecified"
|
||||
}
|
||||
trimmed := strings.TrimPrefix(name, "CHAIN_NETWORK_")
|
||||
if trimmed == "" {
|
||||
return "unspecified"
|
||||
}
|
||||
return strings.ToLower(trimmed)
|
||||
}
|
||||
|
||||
// WalletsFromAccounts converts connector accounts to wallet response format.
|
||||
// Used when querying multiple gateways via discovery.
|
||||
func WalletsFromAccounts(logger mlogger.Logger, accounts []*connectorv1.Account, accessToken *TokenData) http.HandlerFunc {
|
||||
dto := walletsResponse{
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
}
|
||||
dto.Wallets = make([]wallet, 0, len(accounts))
|
||||
for _, acc := range accounts {
|
||||
if acc == nil {
|
||||
continue
|
||||
}
|
||||
dto.Wallets = append(dto.Wallets, accountToWallet(acc))
|
||||
}
|
||||
return response.Ok(logger, dto)
|
||||
}
|
||||
|
||||
func accountToWallet(acc *connectorv1.Account) wallet {
|
||||
if acc == nil {
|
||||
return wallet{}
|
||||
}
|
||||
|
||||
// Extract wallet details from provider details
|
||||
details := map[string]interface{}{}
|
||||
if acc.GetProviderDetails() != nil {
|
||||
details = acc.GetProviderDetails().AsMap()
|
||||
}
|
||||
|
||||
walletRef := ""
|
||||
if ref := acc.GetRef(); ref != nil {
|
||||
walletRef = strings.TrimSpace(ref.GetAccountId())
|
||||
}
|
||||
if v := stringFromDetails(details, "wallet_ref"); v != "" {
|
||||
walletRef = v
|
||||
}
|
||||
|
||||
organizationRef := stringFromDetails(details, "organization_ref")
|
||||
ownerRef := strings.TrimSpace(acc.GetOwnerRef())
|
||||
if v := stringFromDetails(details, "owner_ref"); v != "" {
|
||||
ownerRef = v
|
||||
}
|
||||
|
||||
chain := stringFromDetails(details, "network")
|
||||
tokenSymbol := stringFromDetails(details, "token_symbol")
|
||||
contractAddress := stringFromDetails(details, "contract_address")
|
||||
depositAddress := stringFromDetails(details, "deposit_address")
|
||||
|
||||
name := ""
|
||||
if d := acc.GetDescribable(); d != nil {
|
||||
name = strings.TrimSpace(d.GetName())
|
||||
}
|
||||
if name == "" {
|
||||
name = strings.TrimSpace(acc.GetLabel())
|
||||
}
|
||||
if name == "" {
|
||||
name = walletRef
|
||||
}
|
||||
|
||||
var description *string
|
||||
if d := acc.GetDescribable(); d != nil && d.Description != nil {
|
||||
if trimmed := strings.TrimSpace(d.GetDescription()); trimmed != "" {
|
||||
description = &trimmed
|
||||
}
|
||||
}
|
||||
|
||||
status := acc.GetState().String()
|
||||
// Convert connector state to wallet status format
|
||||
switch acc.GetState() {
|
||||
case connectorv1.AccountState_ACCOUNT_ACTIVE:
|
||||
status = "MANAGED_WALLET_ACTIVE"
|
||||
case connectorv1.AccountState_ACCOUNT_SUSPENDED:
|
||||
status = "MANAGED_WALLET_SUSPENDED"
|
||||
case connectorv1.AccountState_ACCOUNT_CLOSED:
|
||||
status = "MANAGED_WALLET_CLOSED"
|
||||
}
|
||||
|
||||
return wallet{
|
||||
WalletRef: walletRef,
|
||||
OrganizationRef: organizationRef,
|
||||
OwnerRef: ownerRef,
|
||||
Asset: walletAsset{
|
||||
Chain: chain,
|
||||
TokenSymbol: tokenSymbol,
|
||||
ContractAddress: contractAddress,
|
||||
},
|
||||
DepositAddress: depositAddress,
|
||||
Status: status,
|
||||
Name: name,
|
||||
Description: description,
|
||||
CreatedAt: tsToString(acc.GetCreatedAt()),
|
||||
UpdatedAt: tsToString(acc.GetUpdatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func stringFromDetails(details map[string]interface{}, key string) string {
|
||||
if details == nil {
|
||||
return ""
|
||||
}
|
||||
if value, ok := details[key]; ok {
|
||||
return strings.TrimSpace(fmt.Sprint(value))
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WalletBalanceFromConnector converts connector balance to wallet balance response format.
|
||||
// Used when querying gateways via discovery.
|
||||
func WalletBalanceFromConnector(logger mlogger.Logger, bal *connectorv1.Balance, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, walletBalanceResponse{
|
||||
Balance: connectorBalanceToWalletBalance(bal),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func connectorBalanceToWalletBalance(b *connectorv1.Balance) walletBalance {
|
||||
if b == nil {
|
||||
return walletBalance{}
|
||||
}
|
||||
return walletBalance{
|
||||
Available: connectorMoneyToModel(b.GetAvailable()),
|
||||
PendingInbound: connectorMoneyToModel(b.GetPendingInbound()),
|
||||
PendingOutbound: connectorMoneyToModel(b.GetPendingOutbound()),
|
||||
CalculatedAt: tsToString(b.GetCalculatedAt()),
|
||||
}
|
||||
}
|
||||
|
||||
func connectorMoneyToModel(m *moneyv1.Money) *paymenttypes.Money {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
return &paymenttypes.Money{
|
||||
Amount: m.GetAmount(),
|
||||
Currency: m.GetCurrency(),
|
||||
}
|
||||
}
|
||||
57
api/edge/bff/interface/api/sresponse/ws/response.go
Normal file
57
api/edge/bff/interface/api/sresponse/ws/response.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "github.com/tech/sendico/pkg/api/http"
|
||||
r "github.com/tech/sendico/pkg/api/http/response"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/server/interface/api/ws"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
func respond(logger mlogger.Logger, conn *websocket.Conn, messageType, apiStatus, requestID string, data any) {
|
||||
message := ws.Message{
|
||||
BaseResponse: r.BaseResponse{
|
||||
Status: apiStatus,
|
||||
Data: data,
|
||||
},
|
||||
ID: requestID,
|
||||
MessageType: messageType,
|
||||
}
|
||||
|
||||
if err := websocket.JSON.Send(conn, message); err != nil {
|
||||
logger.Warn("Failed to send error message", zap.Error(err), zap.Any("message", message))
|
||||
}
|
||||
}
|
||||
|
||||
func errorf(logger mlogger.Logger, messageType, requestID string, conn *websocket.Conn, resp r.ErrorResponse) {
|
||||
logger.Debug(
|
||||
"Writing error sresponse",
|
||||
zap.String("error", resp.Error),
|
||||
zap.String("details", resp.Details),
|
||||
zap.Int("code", resp.Code),
|
||||
)
|
||||
respond(logger, conn, messageType, api.MSError, requestID, &resp)
|
||||
}
|
||||
|
||||
func Ok(logger mlogger.Logger, requestID string, data any) ws.ResponseHandler {
|
||||
res := func(messageType string, conn *websocket.Conn) {
|
||||
logger.Debug("Successfully executed request", zap.Any("sresponse", data))
|
||||
respond(logger, conn, messageType, api.MSSuccess, requestID, data)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func Internal(logger mlogger.Logger, requestID string, err error) ws.ResponseHandler {
|
||||
res := func(messageType string, conn *websocket.Conn) {
|
||||
errorf(logger, messageType, requestID, conn,
|
||||
r.ErrorResponse{
|
||||
Error: "internal_error",
|
||||
Details: err.Error(),
|
||||
Code: http.StatusInternalServerError,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
||||
Reference in New Issue
Block a user