brokeyourbike/clearbank-api-client-go

View on GitHub
mccy_accounts.go

Summary

Maintainability
A
0 mins
Test Coverage
F
58%
package clearbank

import (
    "context"
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"

    "github.com/google/uuid"
)

type MCCYAccountsClient interface {
    // accounts
    FetchMCCYAccount(ctx context.Context, accountID uuid.UUID) (MCCYAccountResponse, error)
    FetchMCCYAccounts(ctx context.Context, pageNum int, pageSize int) (MCCYAccountsResponse, error)
    CreateMCCYAccount(ctx context.Context, payload CreateMCCYAccountPayload) (MCCYAccountResponse, error)
    UpdateMCCYAccount(ctx context.Context, accountID uuid.UUID, payload UpdateMCCYAccountPayload) (MCCYAccountResponse, error)
    UpdateMCCYAccountStatus(ctx context.Context, accountID uuid.UUID, payload UpdateMCCYAccountStatusPayload) (MCCYAccountResponse, error)
    EnableMCCYAccountCurrency(ctx context.Context, accountID uuid.UUID, payload EnableMCCYAccountCurrencyPayload) error
    CloseMCCYAccount(ctx context.Context, accountID uuid.UUID, reason MCCYAccountCloseReason) error
    FetchMCCYAccountBalances(ctx context.Context, accountID uuid.UUID) (MCCYAccountBalancesResponse, error)

    // virtual accounts
    FetchMCCYVirtualAccount(ctx context.Context, virtualAccountID uuid.UUID) (MCCYVirtualAccountResponse, error)
    FetchMCCYVirtualAccounts(ctx context.Context, pageNum int, pageSize int) (MCCYVirtualAccountsResponse, error)
    CreateMCCYVirtualAccount(ctx context.Context, payload CreateMCCYVirtualAccountPayload) (MCCYVirtualAccountResponse, error)
    UpdateMCCYVirtualAccount(ctx context.Context, virtualAccountID uuid.UUID, payload UpdateMCCYVirtualAccountPayload) (MCCYVirtualAccountResponse, error)
    UpdateMCCYVirtualAccountStatus(ctx context.Context, virtualAccountID uuid.UUID, payload UpdateMCCYVirtualAccountStatusPayload) (MCCYVirtualAccountResponse, error)
    CloseMCCYVirtualAccount(ctx context.Context, virtualAccountID uuid.UUID, reason MCCYAccountCloseReason) error
}

type MCCYAccountKind string

const (
    MCCYAccountKindYourFunds            MCCYAccountKind = "YourFunds"
    MCCYAccountKindGeneralSegregated    MCCYAccountKind = "GeneralSegregated"
    MCCYAccountKindDesignatedSegregated MCCYAccountKind = "DesignatedSegregated"
    MCCYAccountKindGeneralClient        MCCYAccountKind = "GeneralClient"
    MCCYAccountKindDesignatedClient     MCCYAccountKind = "DesignatedClient"
)

type MCCYAccountStatus string
type MCCYAccountStatusReason string

const (
    MCCYAccountStatusActive                      MCCYAccountStatus       = "Active"
    MCCYAccountStatusSuspended                   MCCYAccountStatus       = "Suspended"
    MCCYAccountStatusClosed                      MCCYAccountStatus       = "Closed"
    MCCYAccountStatusNotProvided                 MCCYAccountStatus       = "NotProvided"
    MCCYAccountStatusReasonAccountHolderBankrupt MCCYAccountStatusReason = "AccountHolderBankrupt"
    MCCYAccountStatusReasonDissatisfiedCustomer  MCCYAccountStatusReason = "DissatisfiedCustomer"
    MCCYAccountStatusReasonFinancialCrime        MCCYAccountStatusReason = "FinancialCrime"
    MCCYAccountStatusReasonFraudFirstParty       MCCYAccountStatusReason = "FraudFirstParty"
    MCCYAccountStatusReasonFraudThirdParty       MCCYAccountStatusReason = "FraudThirdParty"
    MCCYAccountStatusReasonOther                 MCCYAccountStatusReason = "Other"
)

type MCCYAccountIdentifier struct {
    Identifier string `json:"identifier"`
    Kind       string `json:"kind"`
}

type MCCYAccountResponse struct {
    ID                uuid.UUID               `json:"id"`
    Name              string                  `json:"name"`
    Label             string                  `json:"label"`
    Owner             string                  `json:"owner"`
    Kind              MCCYAccountKind         `json:"kind"`
    Currencies        []string                `json:"currencies"`
    Identifiers       []MCCYAccountIdentifier `json:"identifiers"`
    Status            MCCYAccountStatus       `json:"status"`
    StatusReason      string                  `json:"statusReason"`
    StatusInformation string                  `json:"statusInformation"`
}

// GetIdentifier returns first identifier.
func (r MCCYAccountResponse) GetIdentifier() string {
    if len(r.Identifiers) > 0 {
        return r.Identifiers[0].Identifier
    }
    return ""
}

func (c *client) FetchMCCYAccount(ctx context.Context, accountID uuid.UUID) (data MCCYAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodGet, fmt.Sprintf("/v2/Accounts/%s/MC", accountID.String()), nil)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)

    return data, c.do(ctx, req)
}

type MCCYAccountsResponse struct {
    Accounts []MCCYAccountResponse `json:"accounts"`
}

func (c *client) FetchMCCYAccounts(ctx context.Context, pageNum int, pageSize int) (data MCCYAccountsResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodGet, "/mccy/v1/Accounts", nil)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.AddQueryParam("pageNumber", strconv.Itoa(pageNum))
    req.AddQueryParam("pageSize", strconv.Itoa(pageSize))
    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)

    return data, c.do(ctx, req)
}

type RoutingCodeKind string

const RoutingCodeKindGB RoutingCodeKind = "GBSortCode"

type CreateMCCYAccountPayload struct {
    Label       string          `json:"label"`
    Owner       string          `json:"owner"`
    Kind        MCCYAccountKind `json:"kind"`
    Currencies  []string        `json:"currencies"`
    RoutingCode struct {
        Code string          `json:"code"`
        Kind RoutingCodeKind `json:"kind"`
    } `json:"routingCode"`
}

func (c *client) CreateMCCYAccount(ctx context.Context, payload CreateMCCYAccountPayload) (data MCCYAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodPost, "/mccy/v1/Accounts", payload)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusCreated)
    req.DecodeTo(&data)

    return data, c.do(ctx, req)
}

type UpdateMCCYAccountPayload struct {
    Label string `json:"label"`
    Owner string `json:"owner"`
}

func (c *client) UpdateMCCYAccount(ctx context.Context, accountID uuid.UUID, payload UpdateMCCYAccountPayload) (data MCCYAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodPatch, fmt.Sprintf("/mccy/v1/Accounts/%s", accountID.String()), payload)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)

    return data, c.do(ctx, req)
}

type UpdateMCCYAccountStatusPayload struct {
    Status       string `json:"status"`
    StatusReason string `json:"statusReason,omitempty"`
    Information  string `json:"information,omitempty"`
}

func (c *client) UpdateMCCYAccountStatus(ctx context.Context, accountID uuid.UUID, payload UpdateMCCYAccountStatusPayload) (data MCCYAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodPatch, fmt.Sprintf("/mccy/v1/Accounts/%s/status", accountID.String()), payload)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)

    return data, c.do(ctx, req)
}

type EnableMCCYAccountCurrencyPayload struct {
    Currency string `json:"currency"`
}

func (c *client) EnableMCCYAccountCurrency(ctx context.Context, accountID uuid.UUID, payload EnableMCCYAccountCurrencyPayload) error {
    req, err := c.newRequest(ctx, http.MethodPost, fmt.Sprintf("/mccy/v1/Accounts/%s/currencies", accountID.String()), payload)
    if err != nil {
        return fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    return c.do(ctx, req)
}

type MCCYAccountCloseReason string

const (
    MCCYAccountCloseReasonOther                  MCCYAccountCloseReason = "Other"
    MCCYAccountCloseReasonAccountHolderDeceased  MCCYAccountCloseReason = "AccountHolderDeceased"
    MCCYAccountCloseReasonAccountSwitched        MCCYAccountCloseReason = "AccountSwitched"
    MCCYAccountCloseReasonCompanyNoLongerTrading MCCYAccountCloseReason = "CompanyNoLongerTrading"
    MCCYAccountCloseReasonDuplicateAccount       MCCYAccountCloseReason = "DuplicateAccount"
)

func (c *client) CloseMCCYAccount(ctx context.Context, accountID uuid.UUID, reason MCCYAccountCloseReason) error {
    req, err := c.newRequest(ctx, http.MethodDelete, fmt.Sprintf("/mccy/v1/Accounts/%s", accountID.String()), nil)
    if err != nil {
        return fmt.Errorf("failed to create request: %w", err)
    }

    req.AddQueryParam("accountCloseReason", string(reason))
    req.ExpectStatus(http.StatusNoContent)
    return c.do(ctx, req)
}

type MCCYAccountBalance struct {
    Currency  string  `json:"currency"`
    Available float64 `json:"available"`
    Actual    float64 `json:"actual"`
}

type MCCYAccountBalancesResponse struct {
    Balances []MCCYAccountBalance `json:"balances"`
}

func (m *MCCYAccountBalancesResponse) UnmarshalJSON(data []byte) error {
    type payload struct {
        Balances map[string]struct {
            Available float64 `json:"Available"`
            Actual    float64 `json:"Actual"`
        } `json:"balances"`
    }

    var p payload

    if err := json.Unmarshal(data, &p); err != nil {
        return fmt.Errorf("unable to unmarshal MCCY account balances: %w", err)
    }

    for currency, balance := range p.Balances {
        m.Balances = append(m.Balances, MCCYAccountBalance{
            Currency:  currency,
            Available: balance.Available,
            Actual:    balance.Actual,
        })
    }

    return nil
}

func (c *client) FetchMCCYAccountBalances(ctx context.Context, accountID uuid.UUID) (data MCCYAccountBalancesResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodGet, fmt.Sprintf("/mccy/v1/Accounts/%s/balances", accountID.String()), nil)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)

    return data, c.do(ctx, req)
}

type MCCYVirtualAccountResponse struct {
    ID                uuid.UUID               `json:"id"`
    AccountID         uuid.UUID               `json:"accountId"`
    Owner             string                  `json:"owner"`
    Identifiers       []MCCYAccountIdentifier `json:"identifiers"`
    Status            MCCYAccountStatus       `json:"status"`
    StatusReason      string                  `json:"statusReason"`
    StatusInformation string                  `json:"statusInformation"`
}

// GetIdentifier returns first identifier.
func (r MCCYVirtualAccountResponse) GetIdentifier() string {
    if len(r.Identifiers) > 0 {
        return r.Identifiers[0].Identifier
    }
    return ""
}

func (c *client) FetchMCCYVirtualAccount(ctx context.Context, virtualAccountID uuid.UUID) (data MCCYVirtualAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodGet, fmt.Sprintf("/mccy/v1/VirtualAccounts/%s", virtualAccountID.String()), nil)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)
    return data, c.do(ctx, req)
}

type MCCYVirtualAccountsResponse struct {
    VirtualAccounts []MCCYVirtualAccountResponse `json:"virtualAccounts"`
}

func (c *client) FetchMCCYVirtualAccounts(ctx context.Context, pageNum int, pageSize int) (data MCCYVirtualAccountsResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodGet, "/mccy/v1/VirtualAccounts", nil)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.AddQueryParam("pageNumber", strconv.Itoa(pageNum))
    req.AddQueryParam("pageSize", strconv.Itoa(pageSize))
    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)
    return data, c.do(ctx, req)
}

type CreateMCCYVirtualAccountPayload struct {
    AccountID      string `json:"accountId"`
    VirtualAccount struct {
        Owner string `json:"owner"`
    } `json:"virtualAccount"`
}

func (c *client) CreateMCCYVirtualAccount(ctx context.Context, payload CreateMCCYVirtualAccountPayload) (data MCCYVirtualAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodPost, "/mccy/v1/VirtualAccounts", payload)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusCreated)
    req.DecodeTo(&data)
    return data, c.do(ctx, req)
}

type UpdateMCCYVirtualAccountPayload struct {
    Owner string `json:"owner"`
}

func (c *client) UpdateMCCYVirtualAccount(ctx context.Context, virtualAccountID uuid.UUID, payload UpdateMCCYVirtualAccountPayload) (data MCCYVirtualAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodPatch, fmt.Sprintf("/mccy/v1/VirtualAccounts/%s", virtualAccountID.String()), payload)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)
    return data, c.do(ctx, req)
}

type UpdateMCCYVirtualAccountStatusPayload struct {
    Status       string `json:"status"`
    StatusReason string `json:"statusReason,omitempty"`
    Information  string `json:"information,omitempty"`
}

func (c *client) UpdateMCCYVirtualAccountStatus(ctx context.Context, virtualAccountID uuid.UUID, payload UpdateMCCYVirtualAccountStatusPayload) (data MCCYVirtualAccountResponse, err error) {
    req, err := c.newRequest(ctx, http.MethodPatch, fmt.Sprintf("/mccy/v1/VirtualAccounts/%s/status", virtualAccountID.String()), payload)
    if err != nil {
        return data, fmt.Errorf("failed to create request: %w", err)
    }

    req.ExpectStatus(http.StatusOK)
    req.DecodeTo(&data)
    return data, c.do(ctx, req)
}

func (c *client) CloseMCCYVirtualAccount(ctx context.Context, virtualAccountID uuid.UUID, reason MCCYAccountCloseReason) error {
    req, err := c.newRequest(ctx, http.MethodDelete, fmt.Sprintf("/mccy/v1/VirtualAccounts/%s", virtualAccountID.String()), nil)
    if err != nil {
        return fmt.Errorf("failed to create request: %w", err)
    }

    req.AddQueryParam("accountCloseReason", string(reason))
    req.ExpectStatus(http.StatusNoContent)
    return c.do(ctx, req)
}