payment rails
This commit is contained in:
211
api/server/interface/api/srequest/endpoint_union.go
Normal file
211
api/server/interface/api/srequest/endpoint_union.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package srequest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/tech/sendico/pkg/merrors"
|
||||
)
|
||||
|
||||
type EndpointType string
|
||||
|
||||
const (
|
||||
EndpointTypeLedger EndpointType = "ledger"
|
||||
EndpointTypeManagedWallet EndpointType = "managed_wallet"
|
||||
EndpointTypeExternalChain EndpointType = "external_chain"
|
||||
EndpointTypeCard EndpointType = "card"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if e.Type == "" {
|
||||
return merrors.InvalidArgument("endpoint type is required")
|
||||
}
|
||||
if e.Type != 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: 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 (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 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 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user