267 lines
7.5 KiB
Go
267 lines
7.5 KiB
Go
package srequest
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
)
|
|
|
|
type EndpointType string
|
|
|
|
const (
|
|
EndpointTypeLedger EndpointType = "ledger"
|
|
EndpointTypeManagedWallet EndpointType = "managedWallet"
|
|
EndpointTypeExternalChain EndpointType = "cryptoAddress"
|
|
EndpointTypeCard EndpointType = "card"
|
|
EndpointTypeCardToken EndpointType = "cardToken"
|
|
EndpointTypeWallet EndpointType = "wallet"
|
|
EndpointTypeBankAccount EndpointType = "bankAccount"
|
|
EndpointTypeIBAN EndpointType = "iban"
|
|
)
|
|
|
|
// Endpoint is a discriminated union for payment endpoints.
|
|
type Endpoint struct {
|
|
Type EndpointType `json:"type"`
|
|
Data json.RawMessage `json:"data"`
|
|
Metadata map[string]string `json:"metadata,omitempty"`
|
|
}
|
|
|
|
func newEndpoint(kind EndpointType, payload interface{}, metadata map[string]string) (Endpoint, error) {
|
|
data, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return Endpoint{}, merrors.Internal("marshal endpoint payload failed")
|
|
}
|
|
return Endpoint{
|
|
Type: kind,
|
|
Data: data,
|
|
Metadata: cloneStringMap(metadata),
|
|
}, nil
|
|
}
|
|
|
|
func (e Endpoint) decodePayload(expected EndpointType, dst interface{}) error {
|
|
actual := normalizeEndpointType(e.Type)
|
|
if actual == "" {
|
|
return merrors.InvalidArgument("endpoint type is required")
|
|
}
|
|
if actual != expected {
|
|
return merrors.InvalidArgument("expected endpoint type " + string(expected) + ", got " + string(e.Type))
|
|
}
|
|
if len(e.Data) == 0 {
|
|
return merrors.InvalidArgument("endpoint data is required for type " + string(expected))
|
|
}
|
|
if err := json.Unmarshal(e.Data, dst); err != nil {
|
|
return merrors.InvalidArgument("decode " + string(expected) + " endpoint: " + err.Error())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (e *Endpoint) UnmarshalJSON(data []byte) error {
|
|
var envelope struct {
|
|
Type EndpointType `json:"type"`
|
|
Data json.RawMessage `json:"data"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}
|
|
if err := json.Unmarshal(data, &envelope); err == nil {
|
|
if envelope.Type != "" || len(envelope.Data) > 0 {
|
|
if envelope.Type == "" {
|
|
return merrors.InvalidArgument("endpoint type is required")
|
|
}
|
|
*e = Endpoint{
|
|
Type: normalizeEndpointType(envelope.Type),
|
|
Data: envelope.Data,
|
|
Metadata: cloneStringMap(envelope.Metadata),
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
var legacy LegacyPaymentEndpoint
|
|
if err := json.Unmarshal(data, &legacy); err != nil {
|
|
return err
|
|
}
|
|
endpoint, err := LegacyPaymentEndpointToEndpointDTO(&legacy)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if endpoint == nil {
|
|
return merrors.InvalidArgument("endpoint payload is empty")
|
|
}
|
|
*e = *endpoint
|
|
return nil
|
|
}
|
|
|
|
func NewLedgerEndpointDTO(payload LedgerEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeLedger, payload, metadata)
|
|
}
|
|
|
|
func NewManagedWalletEndpointDTO(payload ManagedWalletEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeManagedWallet, payload, metadata)
|
|
}
|
|
|
|
func NewExternalChainEndpointDTO(payload ExternalChainEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeExternalChain, payload, metadata)
|
|
}
|
|
|
|
func NewCardEndpointDTO(payload CardEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeCard, payload, metadata)
|
|
}
|
|
|
|
func NewCardTokenEndpointDTO(payload CardTokenEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeCardToken, payload, metadata)
|
|
}
|
|
|
|
func NewWalletEndpointDTO(payload WalletEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeWallet, payload, metadata)
|
|
}
|
|
|
|
func NewBankAccountEndpointDTO(payload BankAccountEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeBankAccount, payload, metadata)
|
|
}
|
|
|
|
func NewIBANEndpointDTO(payload IBANEndpoint, metadata map[string]string) (Endpoint, error) {
|
|
return newEndpoint(EndpointTypeIBAN, payload, metadata)
|
|
}
|
|
|
|
func (e Endpoint) DecodeLedger() (LedgerEndpoint, error) {
|
|
var payload LedgerEndpoint
|
|
return payload, e.decodePayload(EndpointTypeLedger, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeManagedWallet() (ManagedWalletEndpoint, error) {
|
|
var payload ManagedWalletEndpoint
|
|
return payload, e.decodePayload(EndpointTypeManagedWallet, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeExternalChain() (ExternalChainEndpoint, error) {
|
|
var payload ExternalChainEndpoint
|
|
return payload, e.decodePayload(EndpointTypeExternalChain, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeCard() (CardEndpoint, error) {
|
|
var payload CardEndpoint
|
|
return payload, e.decodePayload(EndpointTypeCard, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeCardToken() (CardTokenEndpoint, error) {
|
|
var payload CardTokenEndpoint
|
|
return payload, e.decodePayload(EndpointTypeCardToken, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeWallet() (WalletEndpoint, error) {
|
|
var payload WalletEndpoint
|
|
return payload, e.decodePayload(EndpointTypeWallet, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeBankAccount() (BankAccountEndpoint, error) {
|
|
var payload BankAccountEndpoint
|
|
return payload, e.decodePayload(EndpointTypeBankAccount, &payload)
|
|
}
|
|
|
|
func (e Endpoint) DecodeIBAN() (IBANEndpoint, error) {
|
|
var payload IBANEndpoint
|
|
return payload, e.decodePayload(EndpointTypeIBAN, &payload)
|
|
}
|
|
|
|
func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint, error) {
|
|
if old == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
count := 0
|
|
var endpoint Endpoint
|
|
var err error
|
|
|
|
if old.Ledger != nil {
|
|
count++
|
|
endpoint, err = NewLedgerEndpointDTO(*old.Ledger, old.Metadata)
|
|
}
|
|
if old.ManagedWallet != nil {
|
|
count++
|
|
endpoint, err = NewManagedWalletEndpointDTO(*old.ManagedWallet, old.Metadata)
|
|
}
|
|
if old.ExternalChain != nil {
|
|
count++
|
|
endpoint, err = NewExternalChainEndpointDTO(*old.ExternalChain, old.Metadata)
|
|
}
|
|
if old.Card != nil {
|
|
count++
|
|
endpoint, err = NewCardEndpointDTO(*old.Card, old.Metadata)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if count == 0 {
|
|
return nil, merrors.InvalidArgument("exactly one endpoint must be set")
|
|
}
|
|
if count > 1 {
|
|
return nil, merrors.InvalidArgument("only one endpoint can be set")
|
|
}
|
|
return &endpoint, nil
|
|
}
|
|
|
|
func EndpointDTOToLegacyPaymentEndpoint(new *Endpoint) (*LegacyPaymentEndpoint, error) {
|
|
if new == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
legacy := &LegacyPaymentEndpoint{
|
|
Metadata: cloneStringMap(new.Metadata),
|
|
}
|
|
|
|
switch normalizeEndpointType(new.Type) {
|
|
case EndpointTypeLedger:
|
|
payload, err := new.DecodeLedger()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
legacy.Ledger = &payload
|
|
case EndpointTypeManagedWallet:
|
|
payload, err := new.DecodeManagedWallet()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
legacy.ManagedWallet = &payload
|
|
case EndpointTypeExternalChain:
|
|
payload, err := new.DecodeExternalChain()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
legacy.ExternalChain = &payload
|
|
case EndpointTypeCard:
|
|
payload, err := new.DecodeCard()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
legacy.Card = &payload
|
|
default:
|
|
return nil, merrors.InvalidArgument("unsupported endpoint type: " + string(new.Type))
|
|
}
|
|
return legacy, nil
|
|
}
|
|
|
|
var endpointTypeAliases = map[EndpointType]EndpointType{
|
|
"managed_wallet": EndpointTypeManagedWallet,
|
|
"external_chain": EndpointTypeExternalChain,
|
|
"card_token": EndpointTypeCardToken,
|
|
"bank_account": EndpointTypeBankAccount,
|
|
}
|
|
|
|
func normalizeEndpointType(t EndpointType) EndpointType {
|
|
if canonical, ok := endpointTypeAliases[t]; ok {
|
|
return canonical
|
|
}
|
|
return t
|
|
}
|
|
|
|
func cloneStringMap(src map[string]string) map[string]string {
|
|
if len(src) == 0 {
|
|
return nil
|
|
}
|
|
dst := make(map[string]string, len(src))
|
|
for k, v := range src {
|
|
dst[k] = v
|
|
}
|
|
return dst
|
|
}
|