From 08f13e600707a9aad87b192daed4fcd16f31e0c9 Mon Sep 17 00:00:00 2001 From: ipinak Date: Sat, 4 Feb 2023 20:04:21 +0200 Subject: [PATCH 1/3] (#10): Add support for wallets service --- auth.go | 33 +--------------- viva_wallet.go | 8 +++- wallet.go | 103 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 32 deletions(-) create mode 100644 wallet.go diff --git a/auth.go b/auth.go index 0186cf3..cb3c9eb 100644 --- a/auth.go +++ b/auth.go @@ -23,7 +23,7 @@ type TokenResponse struct { // later use. func (c Client) Authenticate() (*TokenResponse, error) { uri := c.tokenEndpoint() - auth := authBody(c.Config) + auth := AuthBody(c.Config) grant := []byte("grant_type=client_credentials") req, _ := http.NewRequest("POST", uri, bytes.NewBuffer(grant)) @@ -86,7 +86,7 @@ func (c Client) HasAuthExpired() bool { return now.After(expires) } -func authBody(c Config) string { +func AuthBody(c Config) string { auth := fmt.Sprintf("%s:%s", c.ClientID, c.ClientSecret) return base64.StdEncoding.EncodeToString([]byte(auth)) } @@ -102,33 +102,4 @@ func (c Client) authUri() string { return "https://accounts.vivapayments.com" } -type OAuthRoundTripper struct { - tokenValue token - transport http.RoundTripper -} - -func (c OAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - authType := pickAuth(req) - fmt.Println(authType) - - resp, err := c.transport.RoundTrip(req) - if resp != nil && err == nil { - return resp, nil - } - return nil, err -} -type AuthType int -const ( - oauth AuthType = iota - basicAuth -) - -func pickAuth(r *http.Request) AuthType { - switch r.RequestURI { - case "/": - return oauth - default: - return basicAuth - } -} diff --git a/viva_wallet.go b/viva_wallet.go index cf9ce44..cf2cd74 100644 --- a/viva_wallet.go +++ b/viva_wallet.go @@ -51,7 +51,13 @@ func ApiUri(c Config) string { return "https://api.vivapayments.com" } +func AppUri(c Config) string { + if isDemo(c) { + return "https://demo.vivapayments.com" + } + return "https://www.vivapayments.com" +} + func isDemo(c Config) bool { return c.Demo } - diff --git a/wallet.go b/wallet.go new file mode 100644 index 0000000..ff5e2d6 --- /dev/null +++ b/wallet.go @@ -0,0 +1,103 @@ +package vivawallet + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +func getBalanceTransferUri(c Config, walletID string, targetWalletID string) string { + return fmt.Sprintf("%s/api/wallets/%s/balancetransfer/%s", AppUri(c), walletID, targetWalletID) +} + +type BalanceTransfer struct { + Amount int `json:"amount"` + Description string `json:"description"` + SaleTransactionID string `json:"saleTransactionId"` +} + +type BalanceTransferResponse struct { + DebitTransactionID string `json:"DebitTransactionId"` + CreditTransactionID string `json:"CreditTransactionId"` +} + +func (c Client) BalanceTranfer(walletID string, targetWalletID string, payload BalanceTransfer) (*BalanceTransferResponse, error) { + auth := AuthBody(c.Config) + + uri := getBalanceTransferUri(c.Config, walletID, targetWalletID) + data, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("failed to parse BalanceTransfer %s", err) + } + + req, _ := http.NewRequest("POST", uri, bytes.NewReader(data)) + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", auth)) + req.Header.Add("Content-Type", "application/json") + + resp, httpErr := c.HTTPClient.Do(req) + if httpErr != nil { + return nil, fmt.Errorf("failed to tranfer money %s", httpErr) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to tranfer money with status %d", resp.StatusCode) + } + + body, bodyErr := io.ReadAll(resp.Body) + if bodyErr != nil { + return nil, bodyErr + } + + b := &BalanceTransferResponse{} + if jsonErr := json.Unmarshal(body, b); jsonErr != nil { + return nil, jsonErr + } + return b, nil +} + +func getWalletsUri(c Config) string { + return fmt.Sprintf("%s/api/wallets", AppUri(c)) +} + +type GetWalletsResponse struct { + IBAN string `json:"Iban"` + WalletID string `json:"WalletId"` + IsPrimary bool `json:"IsPrimary"` + Amount float64 `json:"Amount"` + Available float64 `json:"Available"` + Overdraft float64 `json:"Overdraft"` + FriendlyName string `json:"FriendlyName"` + CurrencyCode int `json:"CurrencyCode"` +} + +func (c Client) RetrieveWallet() (*GetWalletsResponse, error) { + auth := AuthBody(c.Config) + + uri := getWalletsUri(c.Config) + + req, _ := http.NewRequest("GET", uri, nil) + req.Header.Add("Authorization", fmt.Sprintf("Basic %s", auth)) + req.Header.Add("Content-Type", "application/json") + + resp, httpErr := c.HTTPClient.Do(req) + if httpErr != nil { + return nil, fmt.Errorf("failed to get wallets %s", httpErr) + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("failed to get wallets with status %d", resp.StatusCode) + } + + body, bodyErr := io.ReadAll(resp.Body) + if bodyErr != nil { + return nil, bodyErr + } + + r := &GetWalletsResponse{} + if jsonErr := json.Unmarshal(body, r); jsonErr != nil { + return nil, jsonErr + } + return r, nil +} From d49e61591384e072c6b60f18bd857ebb57c7c719 Mon Sep 17 00:00:00 2001 From: ipinak Date: Sat, 4 Feb 2023 20:15:56 +0200 Subject: [PATCH 2/3] (#10): Add Response postfix on all structs --- order_payments.go | 10 +++++----- transactions.go | 20 ++++++++++---------- wallet.go | 6 +++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/order_payments.go b/order_payments.go index 4e0bccd..1d05086 100644 --- a/order_payments.go +++ b/order_payments.go @@ -8,7 +8,7 @@ import ( "net/http" ) -type CheckoutOrderRequest struct { +type CheckoutOrder struct { Amount int64 `json:"amount"` CustomerTransactions string `json:"customerTrns"` Customer struct { @@ -32,14 +32,14 @@ type CheckoutOrderRequest struct { CardTokens []string `json:"cardTokens,omitempty"` } -type CheckoutResponse struct { +type CheckoutOrderResponse struct { OrderCode int64 `json:"orderCode"` } // CreateOrderPayment creates a new order payment and returns the `orderCode`. -func (c Client) CreateOrderPayment(order CheckoutOrderRequest) (*CheckoutResponse, error) { +func (c Client) CreateOrderPayment(payload CheckoutOrder) (*CheckoutOrderResponse, error) { uri := checkoutEndpoint(c.Config) - data, err := json.Marshal(order) + data, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to parse order %s", err) } @@ -68,7 +68,7 @@ func (c Client) CreateOrderPayment(order CheckoutOrderRequest) (*CheckoutRespons return nil, bodyErr } - response := &CheckoutResponse{} + response := &CheckoutOrderResponse{} if jsonErr := json.Unmarshal(body, response); jsonErr != nil { return nil, jsonErr } diff --git a/transactions.go b/transactions.go index 6913836..d4bef70 100644 --- a/transactions.go +++ b/transactions.go @@ -9,7 +9,7 @@ import ( "time" ) -type Transaction struct { +type TransactionResponse struct { Email string `json:"email"` Amount int `json:"amount"` OrderCode string `json:"orderCode"` @@ -31,7 +31,7 @@ type Transaction struct { DigitalWalletID int `json:"digitalWalletId"` } -func (c Client) GetTransaction(trxID string) (*Transaction, error) { +func (c Client) GetTransaction(trxID string) (*TransactionResponse, error) { uri := getTransactionUri(c.Config, trxID) // TODO: use RoundTripper to avoid rewriting this @@ -59,7 +59,7 @@ func (c Client) GetTransaction(trxID string) (*Transaction, error) { return nil, bodyErr } - trx := &Transaction{} + trx := &TransactionResponse{} if jsonErr := json.Unmarshal(body, trx); jsonErr != nil { return nil, jsonErr } @@ -67,25 +67,25 @@ func (c Client) GetTransaction(trxID string) (*Transaction, error) { } func getTransactionUri(c Config, trxID string) string { - return fmt.Sprintf("%s/%s/%s", ApiUri(c), "checkout/v2/transactions", trxID) + return fmt.Sprintf("%s/checkout/v2/transactions/%s", ApiUri(c), trxID) } type CreateCardToken struct { TransactionID string `json:"transactionId"` } -type CardToken struct { +type CardTokenResponse struct { Token string `json:"token"` } -func (c Client) CreateCardToken(payload CreateCardToken) (*CardToken, error) { +func (c Client) CreateCardToken(payload CreateCardToken) (*CardTokenResponse, error) { // TODO: use RoundTripper to avoid rewriting this if c.HasAuthExpired() { _, authErr := c.Authenticate() return nil, fmt.Errorf("authentication error %s", authErr) } - uri := getCreateCardToken(c.Config) + uri := getCreateCardTokenUri(c.Config) data, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to parse CreateCardToken %s", err) @@ -110,13 +110,13 @@ func (c Client) CreateCardToken(payload CreateCardToken) (*CardToken, error) { return nil, bodyErr } - cardToken := &CardToken{} + cardToken := &CardTokenResponse{} if jsonErr := json.Unmarshal(body, cardToken); jsonErr != nil { return nil, jsonErr } return cardToken, nil } -func getCreateCardToken(c Config) string { - return fmt.Sprintf("%s/%s", ApiUri(c), "acquiring/v1/cards/tokens") +func getCreateCardTokenUri(c Config) string { + return fmt.Sprintf("%s/acquiring/v1/cards/tokens", ApiUri(c)) } diff --git a/wallet.go b/wallet.go index ff5e2d6..5d74db5 100644 --- a/wallet.go +++ b/wallet.go @@ -61,7 +61,7 @@ func getWalletsUri(c Config) string { return fmt.Sprintf("%s/api/wallets", AppUri(c)) } -type GetWalletsResponse struct { +type GetWalletResponse struct { IBAN string `json:"Iban"` WalletID string `json:"WalletId"` IsPrimary bool `json:"IsPrimary"` @@ -72,7 +72,7 @@ type GetWalletsResponse struct { CurrencyCode int `json:"CurrencyCode"` } -func (c Client) RetrieveWallet() (*GetWalletsResponse, error) { +func (c Client) RetrieveWallet() (*GetWalletResponse, error) { auth := AuthBody(c.Config) uri := getWalletsUri(c.Config) @@ -95,7 +95,7 @@ func (c Client) RetrieveWallet() (*GetWalletsResponse, error) { return nil, bodyErr } - r := &GetWalletsResponse{} + r := &GetWalletResponse{} if jsonErr := json.Unmarshal(body, r); jsonErr != nil { return nil, jsonErr } From a454b4ecbd842e93a9e02a63de045451c8633cd3 Mon Sep 17 00:00:00 2001 From: ipinak Date: Sat, 4 Feb 2023 22:02:30 +0200 Subject: [PATCH 3/3] (#10): fix all calls --- auth.go | 25 +++++++++++++++---------- example/main.go | 32 ++++++++++++++++++++++++-------- order_payments.go | 8 ++++---- viva_wallet.go | 11 ++++++++--- wallet.go | 20 ++++++++++---------- 5 files changed, 61 insertions(+), 35 deletions(-) diff --git a/auth.go b/auth.go index cb3c9eb..4ad3871 100644 --- a/auth.go +++ b/auth.go @@ -58,30 +58,32 @@ func (c Client) Authenticate() (*TokenResponse, error) { // AuthToken returns the token value func (c Client) AuthToken() string { c.lock.RLock() - defer c.lock.RUnlock() t := c.tokenValue.value + + c.lock.RUnlock() return t } // SetToken sets the token value and the expiration time of the token. -func (c Client) SetToken(tokenValue string, expires time.Time) { +func (c Client) SetToken(value string, expires time.Time) { c.lock.Lock() - defer c.lock.Unlock() - c.tokenValue = token{ - value: tokenValue, - expires: expires, - } + c.tokenValue.value = value + c.tokenValue.expires = expires + + c.lock.Unlock() } // HasAuthExpired returns true if the expiry time of the token has passed and false // otherwise. func (c Client) HasAuthExpired() bool { c.lock.RLock() - defer c.lock.RUnlock() expires := c.tokenValue.expires + + c.lock.RUnlock() + now := time.Now() return now.After(expires) } @@ -91,6 +93,11 @@ func AuthBody(c Config) string { return base64.StdEncoding.EncodeToString([]byte(auth)) } +func BasicAuth(c Config) string { + auth := fmt.Sprintf("%s:%s", c.MerchantID, c.APIKey) + return base64.StdEncoding.EncodeToString([]byte(auth)) +} + func (c Client) tokenEndpoint() string { return fmt.Sprintf("%s/%s", c.authUri(), "/connect/token") } @@ -101,5 +108,3 @@ func (c Client) authUri() string { } return "https://accounts.vivapayments.com" } - - diff --git a/example/main.go b/example/main.go index 1b9f3dd..5ba1621 100644 --- a/example/main.go +++ b/example/main.go @@ -7,20 +7,36 @@ import ( ) func main() { - clientID := "" - clientSecret := "" - client := vivawallet.New(clientID, clientSecret, true) + clientID := "yjp82d6eub7hva6y9usesqtuzd8ambj914odu50n49jz3.apps.vivapayments.com" + clientSecret := "ODX4vwQVmeYo373814yYf2p6Vq85yR" + merchantID := "393969b6-c18e-4770-ba9a-2838c2beafee" + apiKey := "YZ}z>_" + client := vivawallet.New(clientID, clientSecret, merchantID, apiKey, true) token, err := client.Authenticate() if err != nil { - fmt.Printf("Error: %s", err.Error()) + fmt.Printf("Error: %s\n", err.Error()) return } - fmt.Printf("Token: %s\n", token.AccessToken) + fmt.Printf("Token: %s\n\n", token.AccessToken) - req := vivawallet.CheckoutOrderRequest{ + req := vivawallet.CheckoutOrder{ Amount: 1000, } - op, _ := client.CreateOrderPayment(req) - fmt.Printf("OrderPayment: %d\n", op.OrderCode) + op, err2 := client.CreateOrderPayment(req) + if err2 != nil { + fmt.Printf("err: %s\n", err2.Error()) + } else { + fmt.Printf("OrderPayment: %d\n", op.OrderCode) + } + + wallets, err3 := client.GetWallets() + if err3 != nil { + fmt.Printf("err: %s\n", err3.Error()) + } else { + for _, w := range wallets { + fmt.Printf("Wallet: %v\n", w) + } + } + } diff --git a/order_payments.go b/order_payments.go index 1d05086..cafbba7 100644 --- a/order_payments.go +++ b/order_payments.go @@ -10,7 +10,7 @@ import ( type CheckoutOrder struct { Amount int64 `json:"amount"` - CustomerTransactions string `json:"customerTrns"` + CustomerTransactions string `json:"customerTrns,omitempty"` Customer struct { Email string `json:"email,omitempty"` FullName string `json:"fullName,omitempty"` @@ -38,7 +38,7 @@ type CheckoutOrderResponse struct { // CreateOrderPayment creates a new order payment and returns the `orderCode`. func (c Client) CreateOrderPayment(payload CheckoutOrder) (*CheckoutOrderResponse, error) { - uri := checkoutEndpoint(c.Config) + uri := checkoutOrderUri(c.Config) data, err := json.Marshal(payload) if err != nil { return nil, fmt.Errorf("failed to parse order %s", err) @@ -76,6 +76,6 @@ func (c Client) CreateOrderPayment(payload CheckoutOrder) (*CheckoutOrderRespons return response, nil } -func checkoutEndpoint(c Config) string { - return fmt.Sprintf("%s/%s", ApiUri(c), "checkout/v2/orders") +func checkoutOrderUri(c Config) string { + return fmt.Sprintf("%s/checkout/v2/orders", ApiUri(c)) } diff --git a/viva_wallet.go b/viva_wallet.go index cf2cd74..d83b777 100644 --- a/viva_wallet.go +++ b/viva_wallet.go @@ -10,6 +10,8 @@ type Config struct { Demo bool ClientID string ClientSecret string + MerchantID string + APIKey string } type token struct { @@ -20,8 +22,8 @@ type token struct { type Client struct { Config Config HTTPClient *http.Client - lock *sync.RWMutex - tokenValue token + lock sync.RWMutex + tokenValue *token } // defaultHTTPTimeout is the default timeout on the http.Client used by the library. @@ -32,14 +34,17 @@ var httpClient = &http.Client{ } // New creates a new viva client -func New(clientID string, clientSecret string, demo bool) *Client { +func New(clientID string, clientSecret string, merchantID string, apiKey string, demo bool) *Client { return &Client{ Config: Config{ Demo: demo, ClientID: clientID, ClientSecret: clientSecret, + MerchantID: merchantID, + APIKey: apiKey, }, HTTPClient: httpClient, + tokenValue: &token{}, } } diff --git a/wallet.go b/wallet.go index 5d74db5..180bbbe 100644 --- a/wallet.go +++ b/wallet.go @@ -24,7 +24,7 @@ type BalanceTransferResponse struct { } func (c Client) BalanceTranfer(walletID string, targetWalletID string, payload BalanceTransfer) (*BalanceTransferResponse, error) { - auth := AuthBody(c.Config) + auth := BasicAuth(c.Config) uri := getBalanceTransferUri(c.Config, walletID, targetWalletID) data, err := json.Marshal(payload) @@ -61,19 +61,19 @@ func getWalletsUri(c Config) string { return fmt.Sprintf("%s/api/wallets", AppUri(c)) } -type GetWalletResponse struct { +type Wallet struct { IBAN string `json:"Iban"` - WalletID string `json:"WalletId"` + WalletID int `json:"WalletId"` IsPrimary bool `json:"IsPrimary"` Amount float64 `json:"Amount"` Available float64 `json:"Available"` Overdraft float64 `json:"Overdraft"` FriendlyName string `json:"FriendlyName"` - CurrencyCode int `json:"CurrencyCode"` + CurrencyCode string `json:"CurrencyCode"` } -func (c Client) RetrieveWallet() (*GetWalletResponse, error) { - auth := AuthBody(c.Config) +func (c Client) GetWallets() ([]Wallet, error) { + auth := BasicAuth(c.Config) uri := getWalletsUri(c.Config) @@ -83,11 +83,11 @@ func (c Client) RetrieveWallet() (*GetWalletResponse, error) { resp, httpErr := c.HTTPClient.Do(req) if httpErr != nil { - return nil, fmt.Errorf("failed to get wallets %s", httpErr) + return nil, fmt.Errorf("failed to get wallet %s", httpErr) } if resp.StatusCode != 200 { - return nil, fmt.Errorf("failed to get wallets with status %d", resp.StatusCode) + return nil, fmt.Errorf("failed to get wallet with status %d", resp.StatusCode) } body, bodyErr := io.ReadAll(resp.Body) @@ -95,8 +95,8 @@ func (c Client) RetrieveWallet() (*GetWalletResponse, error) { return nil, bodyErr } - r := &GetWalletResponse{} - if jsonErr := json.Unmarshal(body, r); jsonErr != nil { + var r []Wallet + if jsonErr := json.Unmarshal(body, &r); jsonErr != nil { return nil, jsonErr } return r, nil