Merge branch 'master' into tests-refactor
This commit is contained in:
commit
2b34b3a75c
@ -12,6 +12,7 @@ DB_SSLMODE=disable
|
|||||||
DB_TIMEZONE=Africa/Nairobi
|
DB_TIMEZONE=Africa/Nairobi
|
||||||
|
|
||||||
#External API Calls
|
#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/
|
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
|
||||||
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
|
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
|
CreateAccountURL string
|
||||||
TrackStatusURL string
|
TrackStatusURL string
|
||||||
BalanceURL string
|
BalanceURL string
|
||||||
|
TrackURL string
|
||||||
)
|
)
|
||||||
|
|
||||||
// LoadConfig initializes the configuration values after environment variables are loaded.
|
// LoadConfig initializes the configuration values after environment variables are loaded.
|
||||||
func LoadConfig() {
|
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/")
|
TrackStatusURL = initializers.GetEnv("TRACK_STATUS_URL", "https://custodial.sarafu.africa/api/track/")
|
||||||
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/")
|
BalanceURL = initializers.GetEnv("BALANCE_URL", "https://custodial.sarafu.africa/api/account/status/")
|
||||||
|
TrackURL = initializers.GetEnv("TRACK_URL", "http://localhost:5003/api/v2/account/status")
|
||||||
}
|
}
|
||||||
|
20
go.mod
20
go.mod
@ -1,20 +1,25 @@
|
|||||||
module git.grassecon.net/urdt/ussd
|
module git.grassecon.net/urdt/ussd
|
||||||
|
|
||||||
go 1.22.6
|
go 1.23.0
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b
|
git.defalsify.org/vise.git v0.2.1-0.20241017112704-307fa6fcdc6b
|
||||||
github.com/alecthomas/assert/v2 v2.2.2
|
github.com/alecthomas/assert/v2 v2.2.2
|
||||||
github.com/peteole/testdata-loader v0.3.0
|
github.com/peteole/testdata-loader v0.3.0
|
||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/joho/godotenv v1.5.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/grassrootseconomics/eth-custodial v1.3.0-beta
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.0 // indirect
|
github.com/jackc/pgx/v5 v5.7.1 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/joho/godotenv v1.5.1
|
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
golang.org/x/crypto v0.27.0 // indirect
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
@ -26,15 +31,18 @@ require (
|
|||||||
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
github.com/alecthomas/participle/v2 v2.0.0 // indirect
|
||||||
github.com/alecthomas/repr v0.2.0 // indirect
|
github.com/alecthomas/repr v0.2.0 // indirect
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // 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/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // 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/objx v0.5.2 // indirect
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // 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/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/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.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.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
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/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 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
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 h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
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 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
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=
|
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/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 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
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.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||||
github.com/jackc/pgx/v5 v5.7.0/go.mod h1:awP1KNnjylvpxHuHP63gzjhnGkI1iw+PMoIwvoleN/8=
|
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
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/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 h1:8jckE9KcyNHgyv/VPoaljvKZE0Rqr8+dPVYH6rfNr9I=
|
||||||
github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljMorYBpqlvt3cS83U=
|
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.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 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
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=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
@ -1,25 +1,36 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.grassecon.net/urdt/ussd/config"
|
"git.grassecon.net/urdt/ussd/config"
|
||||||
"git.grassecon.net/urdt/ussd/internal/models"
|
"git.grassecon.net/urdt/ussd/internal/models"
|
||||||
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
okResponse api.OKResponse
|
||||||
|
errResponse api.ErrResponse
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccountServiceInterface interface {
|
type AccountServiceInterface interface {
|
||||||
CheckBalance(publicKey string) (*models.BalanceResponse, error)
|
CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error)
|
||||||
CreateAccount() (*models.AccountResponse, error)
|
CreateAccount(ctx context.Context) (*api.OKResponse, error)
|
||||||
CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error)
|
CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error)
|
||||||
|
TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type AccountService struct {
|
type AccountService struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckAccountStatus retrieves the status of an account transaction based on the provided tracking ID.
|
type TestAccountService struct {
|
||||||
//
|
}
|
||||||
|
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - trackingId: A unique identifier for the account.This should be obtained from a previous call to
|
// - 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
|
// CreateAccount or a similar function that returns an AccountResponse. The `trackingId` field in the
|
||||||
@ -28,9 +39,9 @@ type AccountService struct {
|
|||||||
// Returns:
|
// 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.
|
// - 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.
|
// - 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) (*models.TrackStatusResponse, error) {
|
func (as *AccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||||
resp, err := http.Get(config.TrackStatusURL + trackingId)
|
resp, err := http.Get(config.BalanceURL + trackingId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -40,18 +51,61 @@ func (as *AccountService) CheckAccountStatus(trackingId string) (*models.TrackSt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var trackResp models.TrackStatusResponse
|
var trackResp models.TrackStatusResponse
|
||||||
err = json.Unmarshal(body, &trackResp)
|
err = json.Unmarshal(body, &trackResp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &trackResp, nil
|
return &trackResp, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (as *AccountService) TrackAccountStatus(ctx context.Context, 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.
|
// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - publicKey: The public key associated with the account whose balance needs to be checked.
|
// - publicKey: The public key associated with the account whose balance needs to be checked.
|
||||||
func (as *AccountService) CheckBalance(publicKey string) (*models.BalanceResponse, error) {
|
func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||||
resp, err := http.Get(config.BalanceURL + publicKey)
|
resp, err := http.Get(config.BalanceURL + publicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -75,20 +129,98 @@ func (as *AccountService) CheckBalance(publicKey string) (*models.BalanceRespons
|
|||||||
// If there is an error during the request or processing, this will be nil.
|
// 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.
|
// - 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) CreateAccount() (*models.AccountResponse, error) {
|
func (as *AccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||||
resp, err := http.Post(config.CreateAccountURL, "application/json", nil)
|
var err error
|
||||||
|
|
||||||
|
// Create a new request
|
||||||
|
req, err := http.NewRequest("POST", config.CreateAccountURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var accountResp models.AccountResponse
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
err = json.Unmarshal(body, &accountResp)
|
err := json.Unmarshal([]byte(body), &errResponse)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) (*api.OKResponse, error) {
|
||||||
|
return &api.OKResponse{
|
||||||
|
Ok: true,
|
||||||
|
Description: "Account creation request received successfully",
|
||||||
|
Result: map[string]any{"publicKey": "0x48ADca309b5085852207FAaf2816eD72B52F527C", "trackingId": "28ebe84d-b925-472c-87ae-bbdfa1fb97be"},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tas *TestAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||||
|
balanceResponse := &models.BalanceResponse{
|
||||||
|
Ok: true,
|
||||||
|
Result: struct {
|
||||||
|
Balance string `json:"balance"`
|
||||||
|
Nonce json.Number `json:"nonce"`
|
||||||
|
}{
|
||||||
|
Balance: "0.003 CELO",
|
||||||
|
Nonce: json.Number("0"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return balanceResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tas *TestAccountService) TrackAccountStatus(ctx context.Context, publicKey string) (*api.OKResponse, error) {
|
||||||
|
return &api.OKResponse{
|
||||||
|
Ok: true,
|
||||||
|
Description: "Account creation succeeded",
|
||||||
|
Result: map[string]any{
|
||||||
|
"active": true,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tas *TestAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||||
|
trackResponse := &models.TrackStatusResponse{
|
||||||
|
Ok: true,
|
||||||
|
Result: struct {
|
||||||
|
Transaction struct {
|
||||||
|
CreatedAt time.Time "json:\"createdAt\""
|
||||||
|
Status string "json:\"status\""
|
||||||
|
TransferValue json.Number "json:\"transferValue\""
|
||||||
|
TxHash string "json:\"txHash\""
|
||||||
|
TxType string "json:\"txType\""
|
||||||
|
}
|
||||||
|
}{
|
||||||
|
Transaction: models.Transaction{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
Status: "SUCCESS",
|
||||||
|
TransferValue: json.Number("0.5"),
|
||||||
|
TxHash: "0x123abc456def",
|
||||||
|
TxType: "transfer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return trackResponse, nil
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/asm"
|
"git.defalsify.org/vise.git/asm"
|
||||||
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/cache"
|
"git.defalsify.org/vise.git/cache"
|
||||||
"git.defalsify.org/vise.git/db"
|
"git.defalsify.org/vise.git/db"
|
||||||
@ -27,6 +28,8 @@ var (
|
|||||||
logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
|
logg = logging.NewVanilla().WithDomain("ussdmenuhandler")
|
||||||
scriptDir = path.Join("services", "registration")
|
scriptDir = path.Join("services", "registration")
|
||||||
translationDir = path.Join(scriptDir, "locale")
|
translationDir = path.Join(scriptDir, "locale")
|
||||||
|
okResponse *api.OKResponse
|
||||||
|
errResponse *api.ErrResponse
|
||||||
backOption = []byte("0")
|
backOption = []byte("0")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -137,13 +140,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 {
|
func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error {
|
||||||
accountResp, err := h.accountService.CreateAccount()
|
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
||||||
data := map[utils.DataTyp]string{
|
okResponse, err := h.accountService.CreateAccount(ctx)
|
||||||
utils.DATA_TRACKING_ID: accountResp.Result.TrackingId,
|
if err != nil {
|
||||||
utils.DATA_PUBLIC_KEY: accountResp.Result.PublicKey,
|
return err
|
||||||
utils.DATA_CUSTODIAL_ID: accountResp.Result.CustodialId.String(),
|
|
||||||
}
|
}
|
||||||
|
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 {
|
for key, value := range data {
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
err := store.WriteEntry(ctx, sessionId, key, []byte(value))
|
err := store.WriteEntry(ctx, sessionId, key, []byte(value))
|
||||||
@ -151,9 +159,8 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
flag_account_created, _ := h.flagManager.GetFlag("flag_account_created")
|
|
||||||
res.FlagSet = append(res.FlagSet, flag_account_created)
|
res.FlagSet = append(res.FlagSet, flag_account_created)
|
||||||
return err
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +199,6 @@ func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resou
|
|||||||
}
|
}
|
||||||
|
|
||||||
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||||
|
|
||||||
accountPIN := string(input)
|
accountPIN := string(input)
|
||||||
// Validate that the PIN is a 4-digit number
|
// Validate that the PIN is a 4-digit number
|
||||||
if !isValidPIN(accountPIN) {
|
if !isValidPIN(accountPIN) {
|
||||||
@ -374,7 +380,6 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
|
|||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(input) == 4 {
|
if len(input) == 4 {
|
||||||
yob := string(input)
|
yob := string(input)
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
@ -440,7 +445,6 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
|
|||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(input) > 0 {
|
if len(input) > 0 {
|
||||||
offerings := string(input)
|
offerings := string(input)
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
@ -466,7 +470,6 @@ func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byt
|
|||||||
// ResetAccountAuthorized resets the account authorization flag after a successful PIN entry.
|
// 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) {
|
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
|
|
||||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||||
@ -476,12 +479,10 @@ func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input
|
|||||||
// CheckIdentifier retrieves the PublicKey from the JSON data file.
|
// CheckIdentifier retrieves the PublicKey from the JSON data file.
|
||||||
func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||||
|
|
||||||
@ -495,12 +496,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) {
|
func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
sessionId, ok := ctx.Value("SessionId").(string)
|
sessionId, ok := ctx.Value("SessionId").(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin")
|
||||||
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
|
||||||
@ -552,28 +551,21 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
|
|||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
store := h.userdataStore
|
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 {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
okResponse, err = h.accountService.TrackAccountStatus(ctx, string(publicKey))
|
||||||
accountStatus, err := h.accountService.CheckAccountStatus(string(trackingId))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error checking account status:", err)
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
if !accountStatus.Ok {
|
|
||||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
status := accountStatus.Result.Transaction.Status
|
isActive := okResponse.Result["active"].(bool)
|
||||||
|
if !ok {
|
||||||
err = store.WriteEntry(ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status))
|
return res, err
|
||||||
if err != nil {
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
if accountStatus.Result.Transaction.Status == "SUCCESS" {
|
if isActive {
|
||||||
res.FlagSet = append(res.FlagSet, flag_account_success)
|
res.FlagSet = append(res.FlagSet, flag_account_success)
|
||||||
res.FlagReset = append(res.FlagReset, flag_account_pending)
|
res.FlagReset = append(res.FlagReset, flag_account_pending)
|
||||||
} else {
|
} else {
|
||||||
@ -660,13 +652,17 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
|||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code := codeFromCtx(ctx)
|
||||||
|
l := gotext.NewLocale(translationDir, code)
|
||||||
|
l.AddDomain("default")
|
||||||
|
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
publicKey, err := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceResponse, err := h.accountService.CheckBalance(string(publicKey))
|
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@ -676,7 +672,8 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
|||||||
}
|
}
|
||||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||||
balance := balanceResponse.Result.Balance
|
balance := balanceResponse.Result.Balance
|
||||||
res.Content = balance
|
|
||||||
|
res.Content = l.Get("Balance: %s\n", balance)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@ -698,7 +695,7 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
balanceResponse, err := h.accountService.CheckBalance(string(publicKey))
|
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@ -816,7 +813,7 @@ func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (res
|
|||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
||||||
|
|
||||||
balanceResp, err := h.accountService.CheckBalance(string(publicKey))
|
balanceResp, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@ -846,7 +843,7 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
|
|||||||
|
|
||||||
amountStr := string(input)
|
amountStr := string(input)
|
||||||
|
|
||||||
balanceRes, err := h.accountService.CheckBalance(string(publicKey))
|
balanceRes, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
||||||
balanceStr := balanceRes.Result.Balance
|
balanceStr := balanceRes.Result.Balance
|
||||||
|
|
||||||
if !balanceRes.Ok {
|
if !balanceRes.Ok {
|
||||||
@ -916,7 +913,7 @@ func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (
|
|||||||
return res, nil
|
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) {
|
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||||
var res resource.Result
|
var res resource.Result
|
||||||
|
|
||||||
@ -925,10 +922,7 @@ func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (res
|
|||||||
return res, fmt.Errorf("missing session")
|
return res, fmt.Errorf("missing session")
|
||||||
}
|
}
|
||||||
|
|
||||||
store := h.userdataStore
|
res.Content = string(sessionId)
|
||||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
|
||||||
|
|
||||||
res.Content = string(publicKey)
|
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
@ -965,13 +959,12 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
|
|||||||
// TODO
|
// TODO
|
||||||
// Use the amount, recipient and sender to call the API and initialize the transaction
|
// Use the amount, recipient and sender to call the API and initialize the transaction
|
||||||
store := h.userdataStore
|
store := h.userdataStore
|
||||||
publicKey, _ := store.ReadEntry(ctx, sessionId, utils.DATA_PUBLIC_KEY)
|
|
||||||
|
|
||||||
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT)
|
amount, _ := store.ReadEntry(ctx, sessionId, utils.DATA_AMOUNT)
|
||||||
|
|
||||||
recipient, _ := store.ReadEntry(ctx, sessionId, utils.DATA_RECIPIENT)
|
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")
|
account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"git.grassecon.net/urdt/ussd/internal/utils"
|
"git.grassecon.net/urdt/ussd/internal/utils"
|
||||||
"github.com/alecthomas/assert/v2"
|
"github.com/alecthomas/assert/v2"
|
||||||
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
testdataloader "github.com/peteole/testdata-loader"
|
testdataloader "github.com/peteole/testdata-loader"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -73,49 +74,41 @@ func TestCreateAccount(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf(err.Error())
|
t.Logf(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create required mocks
|
// Create required mocks
|
||||||
mockDataStore := new(mocks.MockUserDataStore)
|
flag_account_created, err := fm.GetFlag("flag_account_created")
|
||||||
mockCreateAccountService := new(mocks.MockAccountService)
|
|
||||||
expectedResult := resource.Result{}
|
|
||||||
accountCreatedFlag, err := fm.GetFlag("flag_account_created")
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Logf(err.Error())
|
t.Logf(err.Error())
|
||||||
}
|
}
|
||||||
expectedResult.FlagSet = append(expectedResult.FlagSet, accountCreatedFlag)
|
|
||||||
|
|
||||||
// Define session ID and mock data
|
// Define session ID and mock data
|
||||||
sessionId := "session123"
|
sessionId := "session123"
|
||||||
typ := utils.DATA_ACCOUNT_CREATED
|
notFoundErr := db.ErrNotFound{}
|
||||||
fakeError := db.ErrNotFound{}
|
|
||||||
// Create context with session ID
|
|
||||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
||||||
|
|
||||||
// Define expected interactions with the mock
|
tests := []struct {
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, typ).Return([]byte("123"), fakeError)
|
name string
|
||||||
expectedAccountResp := &models.AccountResponse{
|
serverResponse *api.OKResponse
|
||||||
Ok: true,
|
expectedResult resource.Result
|
||||||
Result: struct {
|
|
||||||
CustodialId json.Number `json:"custodialId"`
|
|
||||||
PublicKey string `json:"publicKey"`
|
|
||||||
TrackingId string `json:"trackingId"`
|
|
||||||
}{
|
}{
|
||||||
CustodialId: "12",
|
{
|
||||||
PublicKey: "0x8E0XSCSVA",
|
name: "Test account creation success",
|
||||||
TrackingId: "d95a7e83-196c-4fd0-866fSGAGA",
|
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)
|
for _, tt := range tests {
|
||||||
data := map[utils.DataTyp]string{
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
utils.DATA_TRACKING_ID: expectedAccountResp.Result.TrackingId,
|
|
||||||
utils.DATA_PUBLIC_KEY: expectedAccountResp.Result.PublicKey,
|
|
||||||
utils.DATA_CUSTODIAL_ID: expectedAccountResp.Result.CustodialId.String(),
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value := range data {
|
mockDataStore := new(mocks.MockUserDataStore)
|
||||||
mockDataStore.On("WriteEntry", ctx, sessionId, key, []byte(value)).Return(nil)
|
mockCreateAccountService := new(mocks.MockAccountService)
|
||||||
}
|
|
||||||
|
|
||||||
// Create a Handlers instance with the mock data store
|
// Create a Handlers instance with the mock data store
|
||||||
h := &Handlers{
|
h := &Handlers{
|
||||||
@ -124,6 +117,18 @@ func TestCreateAccount(t *testing.T) {
|
|||||||
flagManager: fm.parser,
|
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
|
// Call the method you want to test
|
||||||
res, err := h.CreateAccount(ctx, "create_account", []byte("some-input"))
|
res, err := h.CreateAccount(ctx, "create_account", []byte("some-input"))
|
||||||
|
|
||||||
@ -131,10 +136,12 @@ func TestCreateAccount(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Assert that the account created flag has been set to the result
|
// 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
|
// Assert that expectations were met
|
||||||
mockDataStore.AssertExpectations(t)
|
mockDataStore.AssertExpectations(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWithPersister(t *testing.T) {
|
func TestWithPersister(t *testing.T) {
|
||||||
@ -510,12 +517,8 @@ func TestGetSender(t *testing.T) {
|
|||||||
mockStore := new(mocks.MockUserDataStore)
|
mockStore := new(mocks.MockUserDataStore)
|
||||||
|
|
||||||
// Define test data
|
// Define test data
|
||||||
sessionId := "session123"
|
sessionId := "254712345678"
|
||||||
ctx := context.WithValue(context.Background(), "SessionId", sessionId)
|
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
|
// Create the Handlers instance with the mock store
|
||||||
h := &Handlers{
|
h := &Handlers{
|
||||||
@ -523,11 +526,10 @@ func TestGetSender(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call the method
|
// Call the method
|
||||||
res, _ := h.GetSender(ctx, "max_amount", []byte("check_balance"))
|
res, _ := h.GetSender(ctx, "get_sender", []byte(""))
|
||||||
|
|
||||||
//Assert that the public key from readentry operation is what was set as the result content.
|
|
||||||
assert.Equal(t, publicKey, res.Content)
|
|
||||||
|
|
||||||
|
//Assert that the sessionId is what was set as the result content.
|
||||||
|
assert.Equal(t, sessionId, res.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAmount(t *testing.T) {
|
func TestGetAmount(t *testing.T) {
|
||||||
@ -1067,12 +1069,20 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input []byte
|
input []byte
|
||||||
|
serverResponse *api.OKResponse
|
||||||
response *models.TrackStatusResponse
|
response *models.TrackStatusResponse
|
||||||
expectedResult resource.Result
|
expectedResult resource.Result
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test when account status is Success",
|
name: "Test when account is on the Sarafu network",
|
||||||
input: []byte("TrackingId1234"),
|
input: []byte("TrackingId1234"),
|
||||||
|
serverResponse: &api.OKResponse{
|
||||||
|
Ok: true,
|
||||||
|
Description: "Account creation succeeded",
|
||||||
|
Result: map[string]any{
|
||||||
|
"active": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
response: &models.TrackStatusResponse{
|
response: &models.TrackStatusResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
Result: struct {
|
Result: struct {
|
||||||
@ -1099,17 +1109,7 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Test when fetching account status is not Success",
|
name: "Test when the account is not yet on the sarafu network",
|
||||||
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",
|
|
||||||
input: []byte("TrackingId1234"),
|
input: []byte("TrackingId1234"),
|
||||||
response: &models.TrackStatusResponse{
|
response: &models.TrackStatusResponse{
|
||||||
Ok: true,
|
Ok: true,
|
||||||
@ -1124,13 +1124,20 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
Transaction: models.Transaction{
|
Transaction: models.Transaction{
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
Status: "IN_NETWORK",
|
Status: "SUCCESS",
|
||||||
TransferValue: json.Number("0.5"),
|
TransferValue: json.Number("0.5"),
|
||||||
TxHash: "0x123abc456def",
|
TxHash: "0x123abc456def",
|
||||||
TxType: "transfer",
|
TxType: "transfer",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
serverResponse: &api.OKResponse{
|
||||||
|
Ok: true,
|
||||||
|
Description: "Account creation succeeded",
|
||||||
|
Result: map[string]any{
|
||||||
|
"active": false,
|
||||||
|
},
|
||||||
|
},
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagSet: []uint32{flag_account_pending},
|
FlagSet: []uint32{flag_account_pending},
|
||||||
FlagReset: []uint32{flag_api_error, flag_account_success},
|
FlagReset: []uint32{flag_api_error, flag_account_success},
|
||||||
@ -1150,9 +1157,10 @@ func TestCheckAccountStatus(t *testing.T) {
|
|||||||
|
|
||||||
status := tt.response.Result.Transaction.Status
|
status := tt.response.Result.Transaction.Status
|
||||||
// Define expected interactions with the mock
|
// 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("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()
|
mockDataStore.On("WriteEntry", ctx, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status)).Return(nil).Maybe()
|
||||||
|
|
||||||
// Call the method under test
|
// Call the method under test
|
||||||
@ -1285,7 +1293,7 @@ func TestResetInvalidAmount(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInitiateTransaction(t *testing.T) {
|
func TestInitiateTransaction(t *testing.T) {
|
||||||
sessionId := "session123"
|
sessionId := "254712345678"
|
||||||
|
|
||||||
fm, err := NewFlagManager(flagsPath)
|
fm, err := NewFlagManager(flagsPath)
|
||||||
|
|
||||||
@ -1308,30 +1316,26 @@ func TestInitiateTransaction(t *testing.T) {
|
|||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input []byte
|
input []byte
|
||||||
PublicKey []byte
|
|
||||||
Recipient []byte
|
Recipient []byte
|
||||||
Amount []byte
|
Amount []byte
|
||||||
status string
|
status string
|
||||||
expectedResult resource.Result
|
expectedResult resource.Result
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Test amount reset",
|
name: "Test initiate transaction",
|
||||||
PublicKey: []byte("0x1241527192"),
|
|
||||||
Amount: []byte("0.002 CELO"),
|
Amount: []byte("0.002 CELO"),
|
||||||
Recipient: []byte("0x12415ass27192"),
|
Recipient: []byte("0x12415ass27192"),
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
FlagReset: []uint32{account_authorized_flag},
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
// Define expected interactions with the mock
|
// 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_AMOUNT).Return(tt.Amount, nil)
|
||||||
mockDataStore.On("ReadEntry", ctx, sessionId, utils.DATA_RECIPIENT).Return(tt.Recipient, 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
|
// Call the method under test
|
||||||
res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", tt.input)
|
res, _ := h.InitiateTransaction(ctx, "transaction_reset_amount", tt.input)
|
||||||
@ -1344,10 +1348,8 @@ func TestInitiateTransaction(t *testing.T) {
|
|||||||
|
|
||||||
// Assert that expectations were met
|
// Assert that expectations were met
|
||||||
mockDataStore.AssertExpectations(t)
|
mockDataStore.AssertExpectations(t)
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestQuit(t *testing.T) {
|
func TestQuit(t *testing.T) {
|
||||||
@ -1626,7 +1628,6 @@ func TestValidateRecipient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckBalance(t *testing.T) {
|
func TestCheckBalance(t *testing.T) {
|
||||||
|
|
||||||
sessionId := "session123"
|
sessionId := "session123"
|
||||||
publicKey := "0X13242618721"
|
publicKey := "0X13242618721"
|
||||||
fm, _ := NewFlagManager(flagsPath)
|
fm, _ := NewFlagManager(flagsPath)
|
||||||
@ -1668,7 +1669,7 @@ func TestCheckBalance(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
expectedResult: resource.Result{
|
expectedResult: resource.Result{
|
||||||
Content: "0.003 CELO",
|
Content: "Balance: 0.003 CELO\n",
|
||||||
FlagReset: []uint32{flag_api_error},
|
FlagReset: []uint32{flag_api_error},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1701,10 +1702,8 @@ func TestCheckBalance(t *testing.T) {
|
|||||||
|
|
||||||
//Assert that the result set to content is what was expected
|
//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")
|
assert.Equal(t, res, tt.expectedResult, "Result should contain flags set according to user input")
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetProfile(t *testing.T) {
|
func TestGetProfile(t *testing.T) {
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
type AccountResponse struct {
|
type AccountResponse struct {
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
|
Description string `json:"description"` // Include the description field
|
||||||
Result struct {
|
Result struct {
|
||||||
CustodialId json.Number `json:"custodialId"`
|
|
||||||
PublicKey string `json:"publicKey"`
|
PublicKey string `json:"publicKey"`
|
||||||
TrackingId string `json:"trackingId"`
|
TrackingId string `json:"trackingId"`
|
||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
package mocks
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"git.grassecon.net/urdt/ussd/internal/models"
|
"git.grassecon.net/urdt/ussd/internal/models"
|
||||||
|
"github.com/grassrootseconomics/eth-custodial/pkg/api"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,17 +13,22 @@ type MockAccountService struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) {
|
func (m *MockAccountService) CreateAccount(ctx context.Context) (*api.OKResponse, error) {
|
||||||
args := m.Called()
|
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) (*models.BalanceResponse, error) {
|
func (m *MockAccountService) CheckBalance(ctx context.Context, publicKey string) (*models.BalanceResponse, error) {
|
||||||
args := m.Called(publicKey)
|
args := m.Called(publicKey)
|
||||||
return args.Get(0).(*models.BalanceResponse), args.Error(1)
|
return args.Get(0).(*models.BalanceResponse), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAccountService) CheckAccountStatus(trackingId string) (*models.TrackStatusResponse, error) {
|
func (m *MockAccountService) CheckAccountStatus(ctx context.Context, trackingId string) (*models.TrackStatusResponse, error) {
|
||||||
args := m.Called(trackingId)
|
args := m.Called(trackingId)
|
||||||
return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
|
return args.Get(0).(*models.TrackStatusResponse), args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *MockAccountService) TrackAccountStatus(ctx context.Context,publicKey string) (*api.OKResponse, error) {
|
||||||
|
args := m.Called(publicKey)
|
||||||
|
return args.Get(0).(*api.OKResponse), args.Error(1)
|
||||||
|
}
|
||||||
|
@ -7,6 +7,8 @@ msgstr "Ombi lako limetumwa. %s atapokea %s kutoka kwa %s."
|
|||||||
msgid "Thank you for using Sarafu. Goodbye!"
|
msgid "Thank you for using Sarafu. Goodbye!"
|
||||||
msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
|
msgstr "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
|
||||||
|
|
||||||
|
|
||||||
msgid "For more help,please call: 0757628885"
|
msgid "For more help,please call: 0757628885"
|
||||||
msgstr "Kwa usaidizi zaidi,piga: 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