api-context #127
@ -12,6 +12,7 @@ DB_SSLMODE=disable
|
||||
DB_TIMEZONE=Africa/Nairobi
|
||||
|
||||
#External API Calls
|
||||
CREATE_ACCOUNT_URL=https://custodial.sarafu.africa/api/account/create
|
||||
CREATE_ACCOUNT_URL=http://localhost:5003/api/v2/account/create
|
||||
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
|
||||
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
|
||||
TRACK_URL=http://localhost:5003/api/v2/account/status
|
||||
|
@ -6,11 +6,13 @@ var (
|
||||
CreateAccountURL string
|
||||
TrackStatusURL string
|
||||
BalanceURL string
|
||||
TrackURL string
|
||||
)
|
||||
|
||||
// LoadConfig initializes the configuration values after environment variables are loaded.
|
||||
func LoadConfig() {
|
||||
CreateAccountURL = initializers.GetEnv("CREATE_ACCOUNT_URL", "https://custodial.sarafu.africa/api/account/create")
|
||||
CreateAccountURL = initializers.GetEnv("CREATE_ACCOUNT_URL", "http://localhost:5003/api/v2/account/create")
|
||||
TrackStatusURL = initializers.GetEnv("TRACK_STATUS_URL", "https://custodial.sarafu.africa/api/track/")
|
||||
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/")
|
||||
TrackURL = initializers.GetEnv("TRACK_URL", "http://localhost:5003/api/v2/account/status")
|
||||
}
|
||||
|
23
go.mod
23
go.mod
@ -1,12 +1,15 @@
|
||||
module git.grassecon.net/urdt/ussd
|
||||
|
||||
go 1.22.6
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b
|
||||
github.com/alecthomas/assert/v2 v2.2.2
|
||||
github.com/peteole/testdata-loader v0.3.0
|
||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
@ -15,26 +18,40 @@ require (
|
||||
github.com/jackc/pgx/v5 v5.7.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kr/text v0.2.0
|
||||
|
||||
require (
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
)
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
||||
github.com/alecthomas/repr v0.2.0 // indirect
|
||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
|
||||
)
|
||||
|
17
go.sum
17
go.sum
@ -10,12 +10,14 @@ github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YV
|
||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
@ -24,10 +26,10 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.0 h1:FG6VLIdzvAPhnYqP14sQ2xhFLkiUQHCs6ySqO91kF4g=
|
||||
github.com/jackc/pgx/v5 v5.7.0/go.mod h1:awP1KNnjylvpxHuHP63gzjhnGkI1iw+PMoIwvoleN/8=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
@ -40,8 +42,9 @@ github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNL
|
||||
github.com/pashagolub/pgxmock/v4 v4.3.0/go.mod h1:9VoVHXwS3XR/yPtKGzwQvwZX1kzGB9sM8SviDcHDa3A=
|
||||
github.com/peteole/testdata-loader v0.3.0 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
|
||||
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -3,18 +3,27 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/config"
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||
)
|
||||
|
||||
var (
|
||||
okResponse api.OKResponse
|
||||
errResponse api.ErrResponse
|
||||
)
|
||||
|
||||
type AccountServiceInterface interface {
|
||||
CheckBalance(publicKey string, ctx context.Context) (*models.BalanceResponse, error)
|
||||
CreateAccount(ctx context.Context) (*models.AccountResponse, error)
|
||||
CreateAccount(ctx context.Context) (*api.OKResponse, error)
|
||||
CheckAccountStatus(trackingId string, ctx context.Context) (*models.TrackStatusResponse, error)
|
||||
TrackAccountStatus(publicKey string) (*api.OKResponse, error)
|
||||
}
|
||||
|
||||
type AccountService struct {
|
||||
@ -23,8 +32,6 @@ type AccountService struct {
|
||||
type TestAccountService struct {
|
||||
}
|
||||
|
||||
// CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID.
|
||||
//
|
||||
// Parameters:
|
||||
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
|
||||
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
|
||||
|
||||
@ -33,9 +40,9 @@ type TestAccountService struct {
|
||||
// Returns:
|
||||
// - string: The status of the transaction as a string. If there is an error during the request or processing, this will be an empty string.
|
||||
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
|
||||
// If no error occurs, this will be nil.
|
||||
// If no error occurs, this will be nil
|
||||
func (as *AccountService) CheckAccountStatus(trackingId string, ctx context.Context) (*models.TrackStatusResponse, error) {
|
||||
resp, err := http.Get(config.TrackStatusURL + trackingId)
|
||||
resp, err := http.Get(config.BalanceURL + trackingId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -45,12 +52,55 @@ func (as *AccountService) CheckAccountStatus(trackingId string, ctx context.Cont
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var trackResp models.TrackStatusResponse
|
||||
err = json.Unmarshal(body, &trackResp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &trackResp, nil
|
||||
|
||||
}
|
||||
|
||||
func (as *AccountService) TrackAccountStatus(publicKey string) (*api.OKResponse, error) {
|
||||
var err error
|
||||
// Construct the URL with the path parameter
|
||||
url := fmt.Sprintf("%s/%s", config.TrackURL, publicKey)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-GE-KEY", "xd")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
errResponse.Description = err.Error()
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
err := json.Unmarshal([]byte(body), &errResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, errors.New(errResponse.Description)
|
||||
}
|
||||
err = json.Unmarshal([]byte(body), &okResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(okResponse.Result) == 0 {
|
||||
return nil, errors.New("Empty api result")
|
||||
}
|
||||
return &okResponse, nil
|
||||
|
||||
}
|
||||
|
||||
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
|
||||
@ -80,41 +130,55 @@ func (as *AccountService) CheckBalance(publicKey string, ctx context.Context) (*
|
||||
// If there is an error during the request or processing, this will be nil.
|
||||
// - error: An error if any occurred during the HTTP request, reading the response, or unmarshalling the JSON data.
|
||||
// If no error occurs, this will be nil.
|
||||
func (as *AccountService) CreateAccount(ctx context.Context) (*models.AccountResponse, error) {
|
||||
resp, err := http.Post(config.CreateAccountURL, "application/json", nil)
|
||||
func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
var err error
|
||||
|
||||
// Create a new request
|
||||
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-GE-KEY", "xd")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
errResponse.Description = err.Error()
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var accountResp models.AccountResponse
|
||||
err = json.Unmarshal(body, &accountResp)
|
||||
if resp.StatusCode >= http.StatusBadRequest {
|
||||
err := json.Unmarshal([]byte(body), &errResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &accountResp, nil
|
||||
return nil, errors.New(errResponse.Description)
|
||||
}
|
||||
err = json.Unmarshal([]byte(body), &okResponse)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(okResponse.Result) == 0 {
|
||||
return nil, errors.New("Empty api result")
|
||||
}
|
||||
return &okResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*models.AccountResponse, error) {
|
||||
return &models.AccountResponse{
|
||||
func (tas *TestAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
CustodialId json.Number `json:"custodialId"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
TrackingId string `json:"trackingId"`
|
||||
}{
|
||||
CustodialId: json.Number("182"),
|
||||
PublicKey: "0x48ADca309b5085852207FAaf2816eD72B52F527C",
|
||||
TrackingId: "28ebe84d-b925-472c-87ae-bbdfa1fb97be",
|
||||
},
|
||||
Description: "Account creation request received successfully",
|
||||
Result: map[string]any{"publicKey": "0x48ADca309b5085852207FAaf2816eD72B52F527C", "trackingId": "28ebe84d-b925-472c-87ae-bbdfa1fb97be"},
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckBalance(publicKey string, ctx context.Context) (*models.BalanceResponse, error) {
|
||||
|
||||
balanceResponse := &models.BalanceResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
@ -125,10 +189,29 @@ func (tas *TestAccountService) CheckBalance(publicKey string, ctx context.Contex
|
||||
Nonce: json.Number("0"),
|
||||
},
|
||||
}
|
||||
|
||||
return balanceResponse, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) TrackAccountStatus(publicKey string) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) TrackAccountStatus(publicKey ,) (*api.OKResponse, error) {
|
||||
return &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": true,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tas *TestAccountService) CheckAccountStatus(trackingId string, ctx context.Context) (*models.TrackStatusResponse, error) {
|
||||
trackResponse := &models.TrackStatusResponse{
|
||||
Ok: true,
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.defalsify.org/vise.git/asm"
|
||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||
|
||||
"git.defalsify.org/vise.git/cache"
|
||||
"git.defalsify.org/vise.git/db"
|
||||
@ -27,6 +28,8 @@ var (
|
||||
logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
|
||||
scriptDir = path.Join("services", "registration")
|
||||
translationDir = path.Join(scriptDir, "locale")
|
||||
okResponse *api.OKResponse
|
||||
errResponse *api.ErrResponse
|
||||
)
|
||||
|
||||
// FlagManager handles centralized flag management
|
||||
@ -136,13 +139,18 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
|
||||
}
|
||||
|
||||
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||
accountResp, err := h.accountService.CreateAccount(ctx)
|
||||
data := map[utils.DataTyp]string{
|
||||
utils.DATA_TRACKING_ID: accountResp.Result.TrackingId,
|
||||
utils.DATA_PUBLIC_KEY: accountResp.Result.PublicKey,
|
||||
utils.DATA_CUSTODIAL_ID: accountResp.Result.CustodialId.String(),
|
||||
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||
okResponse, err := h.accountService.CreateAccount(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
trackingId := okResponse.Result["trackingId"].(string)
|
||||
publicKey := okResponse.Result["publicKey"].(string)
|
||||
|
||||
data := map[utils.DataTyp]string{
|
||||
utils.DATA_TRACKING_ID: trackingId,
|
||||
utils.DATA_PUBLIC_KEY: publicKey,
|
||||
}
|
||||
for key, value := range data {
|
||||
store := h.userdataStore
|
||||
err := store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||
@ -150,9 +158,8 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
|
||||
return err
|
||||
}
|
||||
}
|
||||
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||
return err
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
@ -191,7 +198,6 @@ func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resou
|
||||
}
|
||||
|
||||
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||
|
||||
accountPIN := string(input)
|
||||
// Validate that the PIN is a 4-digit number
|
||||
if !isValidPIN(accountPIN) {
|
||||
@ -368,7 +374,6 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
if len(input) == 4 {
|
||||
yob := string(input)
|
||||
store := h.userdataStore
|
||||
@ -411,7 +416,6 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
gender := strings.Split(symbol, "_")[1]
|
||||
store := h.userdataStore
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_GENDER, []byte(gender))
|
||||
@ -430,7 +434,6 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
if len(input) > 0 {
|
||||
offerings := string(input)
|
||||
store := h.userdataStore
|
||||
@ -456,7 +459,6 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
|
||||
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
|
||||
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
@ -466,12 +468,10 @@ func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input
|
||||
// CheckIdentifier retrieves the PublicKey from the JSON data file.
|
||||
func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||
|
||||
@ -485,12 +485,10 @@ func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte
|
||||
func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||
@ -542,28 +540,21 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
trackingId, err := store.ReadEntry(ctx, sessionId, utils.DATA_TRACKING_ID)
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
accountStatus, err := h.accountService.CheckAccountStatus(string(trackingId),ctx)
|
||||
okResponse, err = h.accountService.TrackAccountStatus(string(publicKey),ctx)
|
||||
if err != nil {
|
||||
fmt.Println("Error checking account status:", err)
|
||||
return res, err
|
||||
}
|
||||
if !accountStatus.Ok {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
return res, err
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
status := accountStatus.Result.Transaction.Status
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status))
|
||||
if err != nil {
|
||||
return res, nil
|
||||
isActive := okResponse.Result["active"].(bool)
|
||||
if !ok {
|
||||
return res, err
|
||||
}
|
||||
if accountStatus.Result.Transaction.Status == "SUCCESS" {
|
||||
if isActive {
|
||||
res.FlagSet = append(res.FlagSet, flag_account_success)
|
||||
res.FlagReset = append(res.FlagReset, flag_account_pending)
|
||||
} else {
|
||||
@ -650,6 +641,10 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
store := h.userdataStore
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
@ -666,7 +661,8 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
balance := balanceResponse.Result.Balance
|
||||
res.Content = balance
|
||||
|
||||
res.Content = l.Get("Balance: %s\n", balance)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@ -906,7 +902,7 @@ func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetSender retrieves the public key from the Gdbm Db
|
||||
// GetSender returns the sessionId (phoneNumber)
|
||||
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
@ -915,10 +911,7 @@ func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (res
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
store := h.userdataStore
|
||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||
|
||||
res.Content = string(publicKey)
|
||||
res.Content = string(sessionId)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@ -955,13 +948,12 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
|
||||
// TODO
|
||||
// Use the amount, recipient and sender to call the API and initialize the transaction
|
||||
store := h.userdataStore
|
||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||
|
||||
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT)
|
||||
|
||||
recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT)
|
||||
|
||||
res.Content = l.Get("Your request has been sent. %s will receive %s from %s.", string(recipient), string(amount), string(publicKey))
|
||||
res.Content = l.Get("Your request has been sent. %s will receive %s from %s.", string(recipient), string(amount), string(sessionId))
|
||||
|
||||
account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized")
|
||||
if err != nil {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
"git.grassecon.net/urdt/ussd/internal/utils"
|
||||
"github.com/alecthomas/assert/v2"
|
||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||
testdataloader "github.com/peteole/testdata-loader"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -72,49 +73,41 @@ func TestCreateAccount(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
|
||||
// Create required mocks
|
||||
mockDataStore := new(mocks.MockUserDataStore)
|
||||
mockCreateAccountService := new(mocks.MockAccountService)
|
||||
expectedResult := resource.Result{}
|
||||
accountCreatedFlag, err := fm.GetFlag("flag_account_created")
|
||||
|
||||
flag_account_created, err := fm.GetFlag("flag_account_created")
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
expectedResult.FlagSet = append(expectedResult.FlagSet, accountCreatedFlag)
|
||||
|
||||
// Define session ID and mock data
|
||||
sessionId := "session123"
|
||||
typ := utils.DATA_ACCOUNT_CREATED
|
||||
fakeError := db.ErrNotFound{}
|
||||
// Create context with session ID
|
||||
notFoundErr := db.ErrNotFound{}
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
|
||||
// Define expected interactions with the mock
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return([]byte("123"), fakeError)
|
||||
expectedAccountResp := &models.AccountResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
CustodialId json.Number `json:"custodialId"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
TrackingId string `json:"trackingId"`
|
||||
tests := []struct {
|
||||
name string
|
||||
serverResponse *api.OKResponse
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
CustodialId: "12",
|
||||
PublicKey: "0x8E0XSCSVA",
|
||||
TrackingId: "d95a7e83-196c-4fd0-866fSGAGA",
|
||||
{
|
||||
name: "Test account creation success",
|
||||
serverResponse: &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation successed",
|
||||
Result: map[string]any{
|
||||
"trackingId": "1234567890",
|
||||
"publicKey": "1235QERYU",
|
||||
},
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_account_created},
|
||||
},
|
||||
},
|
||||
}
|
||||
mockCreateAccountService.On("CreateAccount").Return(expectedAccountResp, nil)
|
||||
data := map[utils.DataTyp]string{
|
||||
utils.DATA_TRACKING_ID: expectedAccountResp.Result.TrackingId,
|
||||
utils.DATA_PUBLIC_KEY: expectedAccountResp.Result.PublicKey,
|
||||
utils.DATA_CUSTODIAL_ID: expectedAccountResp.Result.CustodialId.String(),
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
for key, value := range data {
|
||||
mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil)
|
||||
}
|
||||
mockDataStore := new(mocks.MockUserDataStore)
|
||||
mockCreateAccountService := new(mocks.MockAccountService)
|
||||
|
||||
// Create a Handlers instance with the mock data store
|
||||
h := &Handlers{
|
||||
@ -123,6 +116,18 @@ func TestCreateAccount(t *testing.T) {
|
||||
flagManager: fm.parser,
|
||||
}
|
||||
|
||||
data := map[utils.DataTyp]string{
|
||||
utils.DATA_TRACKING_ID: tt.serverResponse.Result["trackingId"].(string),
|
||||
utils.DATA_PUBLIC_KEY: tt.serverResponse.Result["publicKey"].(string),
|
||||
}
|
||||
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_ACCOUNT_CREATED).Return([]byte(""), notFoundErr)
|
||||
mockCreateAccountService.On("CreateAccount").Return(tt.serverResponse, nil)
|
||||
|
||||
for key, value := range data {
|
||||
mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil)
|
||||
}
|
||||
|
||||
// Call the method you want to test
|
||||
res, err := h.CreateAccount(ctx, "create_account", []byte("some-input"))
|
||||
|
||||
@ -130,10 +135,12 @@ func TestCreateAccount(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Assert that the account created flag has been set to the result
|
||||
assert.Equal(t, res, expectedResult, "Expected result should be equal to the actual result")
|
||||
assert.Equal(t, res, tt.expectedResult, "Expected result should be equal to the actual result")
|
||||
|
||||
// Assert that expectations were met
|
||||
mockDataStore.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWithPersister(t *testing.T) {
|
||||
@ -509,12 +516,8 @@ func TestGetSender(t *testing.T) {
|
||||
mockStore := new(mocks.MockUserDataStore)
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
sessionId := "254712345678"
|
||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||
publicKey := "0xcasgatweksalw1018221"
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return([]byte(publicKey), nil)
|
||||
|
||||
// Create the Handlers instance with the mock store
|
||||
h := &Handlers{
|
||||
@ -522,11 +525,10 @@ func TestGetSender(t *testing.T) {
|
||||
}
|
||||
|
||||
// Call the method
|
||||
res, _ := h.GetSender(ctx, "max_amount", []byte("check_balance"))
|
||||
|
||||
//Assert that the public key from readentry operation is what was set as the result content.
|
||||
assert.Equal(t, publicKey, res.Content)
|
||||
res, _ := h.GetSender(ctx, "get_sender", []byte(""))
|
||||
|
||||
//Assert that the sessionId is what was set as the result content.
|
||||
assert.Equal(t, sessionId, res.Content)
|
||||
}
|
||||
|
||||
func TestGetAmount(t *testing.T) {
|
||||
@ -1066,12 +1068,20 @@ func TestCheckAccountStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
serverResponse *api.OKResponse
|
||||
response *models.TrackStatusResponse
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test when account status is Success",
|
||||
name: "Test when account is on the Sarafu network",
|
||||
input: []byte("TrackingId1234"),
|
||||
serverResponse: &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": true,
|
||||
},
|
||||
},
|
||||
response: &models.TrackStatusResponse{
|
||||
Ok: true,
|
||||
Result: struct {
|
||||
@ -1098,17 +1108,7 @@ func TestCheckAccountStatus(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test when fetching account status is not Success",
|
||||
input: []byte("TrackingId1234"),
|
||||
response: &models.TrackStatusResponse{
|
||||
Ok: false,
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_api_error},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test when checking account status api call is a SUCCESS but an account is not yet ready",
|
||||
name: "Test when the account is not yet on the sarafu network",
|
||||
input: []byte("TrackingId1234"),
|
||||
response: &models.TrackStatusResponse{
|
||||
Ok: true,
|
||||
@ -1123,13 +1123,20 @@ func TestCheckAccountStatus(t *testing.T) {
|
||||
}{
|
||||
Transaction: models.Transaction{
|
||||
CreatedAt: time.Now(),
|
||||
Status: "IN_NETWORK",
|
||||
Status: "SUCCESS",
|
||||
TransferValue: json.Number("0.5"),
|
||||
TxHash: "0x123abc456def",
|
||||
TxType: "transfer",
|
||||
},
|
||||
},
|
||||
},
|
||||
serverResponse: &api.OKResponse{
|
||||
Ok: true,
|
||||
Description: "Account creation succeeded",
|
||||
Result: map[string]any{
|
||||
"active": false,
|
||||
},
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
FlagSet: []uint32{flag_account_pending},
|
||||
FlagReset: []uint32{flag_api_error, flag_account_success},
|
||||
@ -1149,9 +1156,10 @@ func TestCheckAccountStatus(t *testing.T) {
|
||||
|
||||
status := tt.response.Result.Transaction.Status
|
||||
// Define expected interactions with the mock
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_TRACKING_ID).Return(tt.input, nil)
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.input, nil)
|
||||
|
||||
mockCreateAccountService.On("CheckAccountStatus", string(tt.input)).Return(tt.response, nil)
|
||||
mockCreateAccountService.On("TrackAccountStatus", string(tt.input)).Return(tt.serverResponse, nil)
|
||||
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status)).Return(nil).Maybe()
|
||||
|
||||
// Call the method under test
|
||||
@ -1284,7 +1292,7 @@ func TestResetInvalidAmount(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestInitiateTransaction(t *testing.T) {
|
||||
sessionId := "session123"
|
||||
sessionId := "254712345678"
|
||||
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
|
||||
@ -1307,30 +1315,26 @@ func TestInitiateTransaction(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input []byte
|
||||
PublicKey []byte
|
||||
Recipient []byte
|
||||
Amount []byte
|
||||
status string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test amount reset",
|
||||
PublicKey: []byte("0x1241527192"),
|
||||
name: "Test initiate transaction",
|
||||
Amount: []byte("0.002 CELO"),
|
||||
Recipient: []byte("0x12415ass27192"),
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{account_authorized_flag},
|
||||
Content: "Your request has been sent. 0x12415ass27192 will receive 0.002CELO from 0x1241527192.",
|
||||
Content: "Your request has been sent. 0x12415ass27192 will receive 0.002 CELO from 254712345678.",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Define expected interactions with the mock
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_PUBLIC_KEY).Return(tt.PublicKey, nil)
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_AMOUNT).Return(tt.Amount, nil)
|
||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_RECIPIENT).Return(tt.Recipient, nil)
|
||||
//mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_AMOUNT, []byte("")).Return(nil)
|
||||
|
||||
// Call the method under test
|
||||
res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", tt.input)
|
||||
@ -1343,10 +1347,8 @@ func TestInitiateTransaction(t *testing.T) {
|
||||
|
||||
// Assert that expectations were met
|
||||
mockDataStore.AssertExpectations(t)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestQuit(t *testing.T) {
|
||||
@ -1625,7 +1627,6 @@ func TestValidateRecipient(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCheckBalance(t *testing.T) {
|
||||
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
fm, _ := NewFlagManager(flagsPath)
|
||||
@ -1667,7 +1668,7 @@ func TestCheckBalance(t *testing.T) {
|
||||
},
|
||||
},
|
||||
expectedResult: resource.Result{
|
||||
Content: "0.003 CELO",
|
||||
Content: "Balance: 0.003 CELO\n",
|
||||
FlagReset: []uint32{flag_api_error},
|
||||
},
|
||||
},
|
||||
@ -1700,10 +1701,8 @@ func TestCheckBalance(t *testing.T) {
|
||||
|
||||
//Assert that the result set to content is what was expected
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetProfile(t *testing.T) {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/internal/models"
|
||||
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
@ -12,9 +13,9 @@ type MockAccountService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockAccountService) CreateAccount(ctx context.Context) (*models.AccountResponse, error) {
|
||||
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||
args := m.Called()
|
||||
return args.Get(0).(*models.AccountResponse), args.Error(1)
|
||||
return args.Get(0).(*api.OKResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAccountService) CheckBalance(publicKey string,ctx context.Context) (*models.BalanceResponse, error) {
|
||||
@ -26,3 +27,8 @@ func (m *MockAccountService) CheckAccountStatus(trackingId string,ctx context.Co
|
||||
args := m.Called(trackingId)
|
||||
return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAccountService) TrackAccountStatus(publicKey string) (*api.OKResponse, error) {
|
||||
args := m.Called(publicKey)
|
||||
return args.Get(0).(*api.OKResponse), args.Error(1)
|
||||
}
|
||||
|
@ -1,14 +1,9 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
)
|
||||
|
||||
type AccountResponse struct {
|
||||
Ok bool `json:"ok"`
|
||||
Description string `json:"description"` // Include the description field
|
||||
Result struct {
|
||||
CustodialId json.Number `json:"custodialId"`
|
||||
PublicKey string `json:"publicKey"`
|
||||
TrackingId string `json:"trackingId"`
|
||||
} `json:"result"`
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Transaction struct {
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Status string `json:"status"`
|
||||
|
@ -7,6 +7,8 @@ msgstr "Ombi lako limetumwa. %s atapokea %s kutoka kwa %s."
|
||||
msgid "Thank you for using Sarafu. Goodbye!"
|
||||
msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
|
||||
|
||||
|
||||
msgid "For more help,please call: 0757628885"
|
||||
msgstr "Kwa usaidizi zaidi,piga: 0757628885"
|
||||
|
||||
msgid "Balance: %s\n"
|
||||
msgstr "Salio: %s\n"
|
||||
|
@ -1 +1 @@
|
||||
Balance: {{.check_balance}}
|
||||
{{.check_balance}}
|
@ -1 +1 @@
|
||||
Salio: {{.check_balance}}
|
||||
{{.check_balance}}
|
Loading…
Reference in New Issue
Block a user
I believe context should per convention always be the first argument.
https://tip.golang.org/blog/context-and-structs#conclusion