package model import ( "bytes" "encoding/json" "fmt" "git.sendico.io/tech/sxchnge/bff/pkg/merrors" "go.mongodb.org/mongo-driver/v2/bson" ) type PaymentType int const ( PaymentTypeIban PaymentType = iota PaymentTypeCard PaymentTypeCardToken PaymentTypeBankAccount PaymentTypeWallet PaymentTypeCryptoAddress PaymentTypeLedger ) var paymentTypeToString = map[PaymentType]string{ PaymentTypeIban: "iban", PaymentTypeCard: "card", PaymentTypeCardToken: "cardToken", PaymentTypeBankAccount: "bankAccount", PaymentTypeWallet: "wallet", PaymentTypeCryptoAddress: "cryptoAddress", PaymentTypeLedger: "ledger", } var paymentTypeFromString = map[string]PaymentType{ "iban": PaymentTypeIban, "card": PaymentTypeCard, "cardToken": PaymentTypeCardToken, "bankAccount": PaymentTypeBankAccount, "wallet": PaymentTypeWallet, "cryptoAddress": PaymentTypeCryptoAddress, "ledger": PaymentTypeLedger, } func (t PaymentType) String() string { if v, ok := paymentTypeToString[t]; ok { return v } return "iban" } func (t PaymentType) IsValid() bool { _, ok := paymentTypeToString[t] return ok } func (t PaymentType) MarshalJSON() ([]byte, error) { return json.Marshal(t.String()) } func (t *PaymentType) UnmarshalJSON(data []byte) error { var val string if err := json.Unmarshal(data, &val); err != nil { return err } v, ok := paymentTypeFromString[val] if !ok { return merrors.InvalidArgument(fmt.Sprintf("unknown PaymentType: %q", val)) } *t = v return nil } type PaymentMethodOwnerKind string const ( PaymentMethodOwnerKindUnspecified PaymentMethodOwnerKind = "" PaymentMethodOwnerKindAccount PaymentMethodOwnerKind = "ACCOUNT" PaymentMethodOwnerKindRecipient PaymentMethodOwnerKind = "RECIPIENT" ) func (k PaymentMethodOwnerKind) String() string { return string(k) } func (k PaymentMethodOwnerKind) IsValid() bool { switch k { case PaymentMethodOwnerKindAccount, PaymentMethodOwnerKindRecipient: return true default: return false } } type PaymentMethod struct { PermissionBound `bson:",inline" json:",inline"` Describable `bson:",inline" json:",inline"` OwnerKind PaymentMethodOwnerKind `bson:"ownerKind" json:"ownerKind"` OwnerRef bson.ObjectID `bson:"ownerRef" json:"ownerRef"` Type PaymentType `bson:"type" json:"type"` Data bson.Raw `bson:"data" json:"data"` IsMain bool `bson:"isMain" json:"isMain"` } type paymentMethodJSON struct { PermissionBound `json:",inline"` Describable `json:",inline"` OwnerKind PaymentMethodOwnerKind `json:"ownerKind"` OwnerRef bson.ObjectID `json:"ownerRef"` Type PaymentType `json:"type"` Data json.RawMessage `json:"data"` IsMain bool `json:"isMain"` } func (m PaymentMethod) MarshalJSON() ([]byte, error) { marshaledData, err := m.marshalDataJSON() if err != nil { return nil, err } payload := paymentMethodJSON{ PermissionBound: m.PermissionBound, Describable: m.Describable, OwnerKind: m.OwnerKind, OwnerRef: m.OwnerRef, Type: m.Type, Data: marshaledData, IsMain: m.IsMain, } return json.Marshal(payload) } func (m *PaymentMethod) UnmarshalJSON(data []byte) error { var payload paymentMethodJSON if err := json.Unmarshal(data, &payload); err != nil { return err } m.PermissionBound = payload.PermissionBound m.Describable = payload.Describable m.OwnerKind = payload.OwnerKind m.OwnerRef = payload.OwnerRef m.Type = payload.Type m.IsMain = payload.IsMain if len(payload.Data) == 0 || bytes.Equal(payload.Data, []byte("null")) { m.Data = nil return nil } if raw, ok, err := m.unmarshalTypedDataJSON(payload.Data); ok || err != nil { if err != nil { return err } m.Data = raw return nil } var rawData map[string]any if err := json.Unmarshal(payload.Data, &rawData); err != nil { return err } raw, err := bson.Marshal(rawData) if err != nil { return err } m.Data = raw return nil } func (m PaymentMethod) marshalDataJSON() (json.RawMessage, error) { if len(m.Data) == 0 { return nil, nil } var payload any var err error switch m.Type { case PaymentTypeIban: payload, err = marshalTypedPaymentData[IbanPaymentData](m.Data) case PaymentTypeCard: payload, err = marshalTypedPaymentData[CardPaymentData](m.Data) case PaymentTypeCardToken: payload, err = marshalTypedPaymentData[TokenPaymentData](m.Data) case PaymentTypeBankAccount: payload, err = marshalTypedPaymentData[RussianBankAccountPaymentData](m.Data) case PaymentTypeWallet: payload, err = marshalTypedPaymentData[WalletPaymentData](m.Data) case PaymentTypeCryptoAddress: payload, err = marshalTypedPaymentData[CryptoAddressPaymentData](m.Data) case PaymentTypeLedger: payload, err = marshalTypedPaymentData[LedgerPaymentData](m.Data) default: var data bson.M if err := bson.Unmarshal(m.Data, &data); err != nil { return nil, err } if data == nil { return nil, nil } payload = data } if err != nil { return nil, err } data, err := json.Marshal(payload) if err != nil { return nil, err } return data, nil } func (m PaymentMethod) unmarshalTypedDataJSON(data json.RawMessage) (bson.Raw, bool, error) { switch m.Type { case PaymentTypeIban: return unmarshalTypedPaymentData[IbanPaymentData](data) case PaymentTypeCard: return unmarshalTypedPaymentData[CardPaymentData](data) case PaymentTypeCardToken: return unmarshalTypedPaymentData[TokenPaymentData](data) case PaymentTypeBankAccount: return unmarshalTypedPaymentData[RussianBankAccountPaymentData](data) case PaymentTypeWallet: return unmarshalTypedPaymentData[WalletPaymentData](data) case PaymentTypeCryptoAddress: return unmarshalTypedPaymentData[CryptoAddressPaymentData](data) case PaymentTypeLedger: return unmarshalTypedPaymentData[LedgerPaymentData](data) default: return nil, false, nil } } func marshalTypedPaymentData[T any](raw bson.Raw) (T, error) { var payload T err := bson.Unmarshal(raw, &payload) return payload, err } func unmarshalTypedPaymentData[T any](data json.RawMessage) (bson.Raw, bool, error) { var payload T if err := json.Unmarshal(data, &payload); err != nil { return nil, true, err } raw, err := bson.Marshal(payload) if err != nil { return nil, true, err } return raw, true, nil } type PaymentStatus string const ( PaymentStatusUnspecified PaymentStatus = "unspecified" // Intent exists, no funds touched yet. PaymentStatusCreated PaymentStatus = "created" // Internal Sendico operations: holds, ledger, FX, fee calc. PaymentStatusAuthorizing PaymentStatus = "authorizing" // Funds are outside Sendico (rail/gateway/blockchain/provider). // Observe lives ONLY in this state. PaymentStatusExecuting PaymentStatus = "executing" // Final states. PaymentStatusSucceeded PaymentStatus = "success" PaymentStatusFailed PaymentStatus = "failed" PaymentStatusCancelled PaymentStatus = "cancelled" )