diff --git a/.gitignore b/.gitignore index 562e720..54ad3ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,6 @@ **/*.env -covprofile \ No newline at end of file +covprofile +go.work* +**/*/*.bin +**/*/.state/ +cmd/.state/ diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..906c4cc --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,190 @@ +package main + +import ( + "context" + "flag" + "fmt" + "os" + "path" + + "git.defalsify.org/vise.git/asm" + "git.defalsify.org/vise.git/db" + fsdb "git.defalsify.org/vise.git/db/fs" + gdbmdb "git.defalsify.org/vise.git/db/gdbm" + "git.defalsify.org/vise.git/engine" + "git.defalsify.org/vise.git/persist" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/logging" + "git.grassecon.net/urdt/ussd/internal/handlers/ussd" +) + +var ( + logg = logging.NewVanilla() + scriptDir = path.Join("services", "registration") +) + +func getParser(fp string, debug bool) (*asm.FlagParser, error) { + flagParser := asm.NewFlagParser().WithDebug() + _, err := flagParser.Load(fp) + if err != nil { + return nil, err + } + return flagParser, nil +} + +func getHandler(appFlags *asm.FlagParser, rs *resource.DbResource, pe *persist.Persister, userdataStore db.Db) (*ussd.Handlers, error) { + + ussdHandlers, err := ussd.NewHandlers(appFlags, pe, userdataStore) + if err != nil { + return nil, err + } + rs.AddLocalFunc("select_language", ussdHandlers.SetLanguage) + rs.AddLocalFunc("create_account", ussdHandlers.CreateAccount) + rs.AddLocalFunc("save_pin", ussdHandlers.SavePin) + rs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin) + rs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier) + rs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus) + rs.AddLocalFunc("authorize_account", ussdHandlers.Authorize) + rs.AddLocalFunc("quit", ussdHandlers.Quit) + rs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance) + rs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient) + rs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset) + rs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount) + rs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount) + rs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount) + rs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient) + rs.AddLocalFunc("get_sender", ussdHandlers.GetSender) + rs.AddLocalFunc("get_amount", ussdHandlers.GetAmount) + rs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin) + rs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname) + rs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname) + rs.AddLocalFunc("save_gender", ussdHandlers.SaveGender) + rs.AddLocalFunc("save_location", ussdHandlers.SaveLocation) + rs.AddLocalFunc("save_yob", ussdHandlers.SaveYob) + rs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings) + rs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance) + rs.AddLocalFunc("reset_account_authorized", ussdHandlers.ResetAccountAuthorized) + rs.AddLocalFunc("reset_allow_update", ussdHandlers.ResetAllowUpdate) + rs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo) + rs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob) + rs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob) + rs.AddLocalFunc("set_reset_single_edit", ussdHandlers.SetResetSingleEdit) + rs.AddLocalFunc("initiate_transaction", ussdHandlers.InitiateTransaction) + + return ussdHandlers, nil +} + +func getPersister(dbDir string, ctx context.Context) (*persist.Persister, error) { + err := os.MkdirAll(dbDir, 0700) + if err != nil { + return nil, fmt.Errorf("state dir create exited with error: %v\n", err) + } + store := gdbmdb.NewGdbmDb() + storeFile := path.Join(dbDir, "state.gdbm") + store.Connect(ctx, storeFile) + pr := persist.NewPersister(store) + return pr, nil +} + +func getUserdataDb(dbDir string, ctx context.Context) db.Db { + store := gdbmdb.NewGdbmDb() + storeFile := path.Join(dbDir, "userdata.gdbm") + store.Connect(ctx, storeFile) + + return store +} + +func getResource(resourceDir string, ctx context.Context) (resource.Resource, error) { + store := fsdb.NewFsDb() + err := store.Connect(ctx, resourceDir) + if err != nil { + return nil, err + } + rfs := resource.NewDbResource(store) + return rfs, nil +} + +func getEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) *engine.DefaultEngine { + en := engine.NewEngine(cfg, rs) + en = en.WithPersister(pr) + return en +} + +func main() { + var dbDir string + var resourceDir string + var size uint + var sessionId string + var debug bool + flag.StringVar(&sessionId, "session-id", "075xx2123", "session id") + flag.StringVar(&dbDir, "dbdir", ".state", "database dir to read from") + flag.StringVar(&resourceDir, "resourcedir", path.Join("services", "registration"), "resource dir") + flag.BoolVar(&debug, "d", false, "use engine debug output") + flag.UintVar(&size, "s", 160, "max size of output") + flag.Parse() + + logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size) + + ctx := context.Background() + ctx = context.WithValue(ctx, "SessionId",sessionId) + pfp := path.Join(scriptDir, "pp.csv") + flagParser, err := getParser(pfp, true) + + if err != nil { + os.Exit(1) + } + + cfg := engine.Config{ + Root: "root", + SessionId: sessionId, + OutputSize: uint32(size), + FlagCount: uint32(16), + } + + rs, err := getResource(resourceDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + pr, err := getPersister(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + store := getUserdataDb(dbDir, ctx) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + dbResource, ok := rs.(*resource.DbResource) + if !ok { + os.Exit(1) + } + + hl, err := getHandler(flagParser, dbResource, pr, store) + if err != nil { + fmt.Fprintf(os.Stderr, err.Error()) + os.Exit(1) + } + + en := getEngine(cfg, rs, pr) + en = en.WithFirst(hl.Init) + if debug { + en = en.WithDebug(nil) + } + + _, err = en.Init(ctx) + if err != nil { + fmt.Fprintf(os.Stderr, "engine init exited with error: %v\n", err) + os.Exit(1) + } + + err = engine.Loop(ctx, en, os.Stdin, os.Stdout) + if err != nil { + fmt.Fprintf(os.Stderr, "loop exited with error: %v\n", err) + os.Exit(1) + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..0571503 --- /dev/null +++ b/config/config.go @@ -0,0 +1,10 @@ +package config + + + +const ( + CreateAccountURL = "https://custodial.sarafu.africa/api/account/create" + TrackStatusURL = "https://custodial.sarafu.africa/api/track/" + BalanceURL = "https://custodial.sarafu.africa/api/account/status/" +) + diff --git a/go-vise b/go-vise new file mode 160000 index 0000000..2bddc36 --- /dev/null +++ b/go-vise @@ -0,0 +1 @@ +Subproject commit 2bddc363f20210ab019eec29d8ba4104d147e9b7 diff --git a/go.mod b/go.mod index 8358c8c..e2aff05 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,25 @@ module git.grassecon.net/urdt/ussd go 1.22.6 + +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/fxamacker/cbor/v2 v2.4.0 // indirect + 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/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 +) + +require ( + git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89 + github.com/alecthomas/assert/v2 v2.2.2 + gopkg.in/leonelquinteros/gotext.v1 v1.3.1 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2624d07 --- /dev/null +++ b/go.sum @@ -0,0 +1,36 @@ +git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89 h1:YyQODhMwSM5YD9yKHM5jCF0HC0RQtE3MkVXcTnOhXJo= +git.defalsify.org/vise.git v0.1.0-rc.1.0.20240906020635-400f69d01a89/go.mod h1:JDguWmcoWBdsnpw7PUjVZAEpdC/ubBmjdUBy3tjP63M= +github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk= +github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ= +github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g= +github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y= +github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk= +github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= +github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE= +github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U= +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/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +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= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= +github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY= +github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk= +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/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc= +gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/handlers/server/accountservice.go b/internal/handlers/server/accountservice.go new file mode 100644 index 0000000..f4375a1 --- /dev/null +++ b/internal/handlers/server/accountservice.go @@ -0,0 +1,112 @@ +package server + +import ( + "encoding/json" + "io" + "net/http" + + "git.grassecon.net/urdt/ussd/config" + "git.grassecon.net/urdt/ussd/internal/models" +) + +type AccountServiceInterface interface { + CheckBalance(publicKey string) (string, error) + CreateAccount() (*models.AccountResponse, error) + CheckAccountStatus(trackingId string) (string, error) +} + +type AccountService 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 +// AccountResponse struct can be used here to check the account status during a transaction. +// +// +// 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. +// +func (as *AccountService) CheckAccountStatus(trackingId string) (string, error) { + resp, err := http.Get(config.TrackStatusURL + trackingId) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + var trackResp models.TrackStatusResponse + err = json.Unmarshal(body, &trackResp) + if err != nil { + return "", err + } + + status := trackResp.Result.Transaction.Status + + return status, nil +} + + +// CheckBalance retrieves the balance for a given public key from the custodial balance API endpoint. +// Parameters: +// - publicKey: The public key associated with the account whose balance needs to be checked. +func (as *AccountService) CheckBalance(publicKey string) (string, error) { + + resp, err := http.Get(config.BalanceURL + publicKey) + if err != nil { + return "0.0", err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "0.0", err + } + + var balanceResp models.BalanceResponse + err = json.Unmarshal(body, &balanceResp) + if err != nil { + return "0.0", err + } + + balance := balanceResp.Result.Balance + return balance, nil +} + + +//CreateAccount creates a new account in the custodial system. +// Returns: +// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account. +// 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() (*models.AccountResponse, error) { + resp, err := http.Post(config.CreateAccountURL, "application/json", nil) + if err != nil { + 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 err != nil { + return nil, err + } + + return &accountResp, nil +} diff --git a/internal/handlers/ussd/menuhandler.go b/internal/handlers/ussd/menuhandler.go new file mode 100644 index 0000000..066eef2 --- /dev/null +++ b/internal/handlers/ussd/menuhandler.go @@ -0,0 +1,933 @@ +package ussd + +import ( + "bytes" + "context" + "fmt" + "path" + "regexp" + "strconv" + "strings" + + "git.defalsify.org/vise.git/asm" + + "git.defalsify.org/vise.git/cache" + "git.defalsify.org/vise.git/db" + "git.defalsify.org/vise.git/lang" + "git.defalsify.org/vise.git/logging" + "git.defalsify.org/vise.git/persist" + "git.defalsify.org/vise.git/resource" + "git.defalsify.org/vise.git/state" + "git.grassecon.net/urdt/ussd/internal/handlers/server" + "git.grassecon.net/urdt/ussd/internal/utils" + "gopkg.in/leonelquinteros/gotext.v1" +) + +var ( + logg = logging.NewVanilla().WithDomain("ussdmenuhandler") + scriptDir = path.Join("services", "registration") + translationDir = path.Join(scriptDir, "locale") +) + +type FSData struct { + Path string + St *state.State +} + +// FlagManager handles centralized flag management +type FlagManager struct { + parser *asm.FlagParser +} + +// NewFlagManager creates a new FlagManager instance +func NewFlagManager(csvPath string) (*FlagManager, error) { + parser := asm.NewFlagParser() + _, err := parser.Load(csvPath) + if err != nil { + return nil, fmt.Errorf("failed to load flag parser: %v", err) + } + + return &FlagManager{ + parser: parser, + }, nil +} + +// GetFlag retrieves a flag value by its label +func (fm *FlagManager) GetFlag(label string) (uint32, error) { + return fm.parser.GetFlag(label) +} + +type Handlers struct { + pe *persist.Persister + st *state.State + ca cache.Memory + userdataStore db.Db + flagManager *asm.FlagParser + accountFileHandler *utils.AccountFileHandler + accountService server.AccountServiceInterface +} + +func NewHandlers(appFlags *asm.FlagParser, pe *persist.Persister, userdataStore db.Db) (*Handlers, error) { + if pe == nil { + return nil, fmt.Errorf("cannot create handler with nil persister") + } + if userdataStore == nil { + return nil, fmt.Errorf("cannot create handler with nil userdata store") + } + h := &Handlers{ + pe: pe, + userdataStore: userdataStore, + flagManager: appFlags, + accountFileHandler: utils.NewAccountFileHandler(userdataStore), + accountService: &server.AccountService{}, + } + return h, nil +} + +// Define the regex pattern as a constant +const pinPattern = `^\d{4}$` + +// isValidPIN checks whether the given input is a 4 digit number +func isValidPIN(pin string) bool { + match, _ := regexp.MatchString(pinPattern, pin) + return match +} + +func (h *Handlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var r resource.Result + + if h.pe == nil { + logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca) + return r, nil + } + h.st = h.pe.GetState() + h.ca = h.pe.GetMemory() + if h.st == nil || h.ca == nil { + logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca) + return r, fmt.Errorf("cannot get state and memory for handler") + } + h.pe = nil + + logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca) + + return r, nil +} + +// SetLanguage sets the language across the menu +func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + + inputStr := string(input) + switch inputStr { + case "0": + res.FlagSet = []uint32{state.FLAG_LANG} + res.Content = "eng" + case "1": + res.FlagSet = []uint32{state.FLAG_LANG} + res.Content = "swa" + default: + } + + languageSetFlag, err := h.flagManager.GetFlag("flag_language_set") + if err != nil { + return res, err + } + res.FlagSet = append(res.FlagSet, languageSetFlag) + + return res, nil +} + +func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, res *resource.Result) error { + accountResp, err := h.accountService.CreateAccount() + 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(), + } + for key, value := range data { + err := utils.WriteEntry(ctx, h.userdataStore, sessionId, key, []byte(value)) + if err != nil { + return err + } + } + flag_account_created, _ := h.flagManager.GetFlag("flag_account_created") + res.FlagSet = append(res.FlagSet, flag_account_created) + return err + +} + +// CreateAccount checks if any account exists on the JSON data file, and if not +// creates an account on the API, +// sets the default values and flags +func (h *Handlers) CreateAccount(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") + } + _, err = utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_CREATED) + if err != nil { + if db.IsNotFound(err) { + logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist") + err = h.createAccountNoExist(ctx, sessionId, &res) + if err != nil { + return res, err + } + } + } + return res, nil +} + +// SavePin persists the user's PIN choice into the filesystem +func (h *Handlers) SavePin(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") + + accountPIN := string(input) + // Validate that the PIN is a 4-digit number + if !isValidPIN(accountPIN) { + res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN, []byte(accountPIN)) + if err != nil { + return res, err + } + + return res, nil +} + +// SetResetSingleEdit sets and resets flags to allow gradual editing of profile information. +func (h *Handlers) SetResetSingleEdit(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + menuOption := string(input) + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + flag_single_edit, _ := h.flagManager.GetFlag("flag_single_edit") + + switch menuOption { + case "2": + res.FlagReset = append(res.FlagReset, flag_allow_update) + res.FlagSet = append(res.FlagSet, flag_single_edit) + case "3": + res.FlagReset = append(res.FlagReset, flag_allow_update) + res.FlagSet = append(res.FlagSet, flag_single_edit) + case "4": + res.FlagReset = append(res.FlagReset, flag_allow_update) + res.FlagSet = append(res.FlagSet, flag_single_edit) + default: + res.FlagReset = append(res.FlagReset, flag_single_edit) + } + + return res, nil +} + +// VerifyPin checks whether the confirmation PIN is similar to the account PIN +// If similar, it sets the USERFLAG_PIN_SET flag allowing the user +// to access the main menu +func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_valid_pin, _ := h.flagManager.GetFlag("flag_valid_pin") + flag_pin_mismatch, _ := h.flagManager.GetFlag("flag_pin_mismatch") + flag_pin_set, _ := h.flagManager.GetFlag("flag_pin_set") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + AccountPin, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN) + + if bytes.Equal(input, AccountPin) { + res.FlagSet = []uint32{flag_valid_pin} + res.FlagReset = []uint32{flag_pin_mismatch} + res.FlagSet = append(res.FlagSet, flag_pin_set) + } else { + res.FlagSet = []uint32{flag_pin_mismatch} + } + + return res, nil +} + +// codeFromCtx retrieves language codes from the context that can be used for handling translations +func codeFromCtx(ctx context.Context) string { + var code string + if ctx.Value("Language") != nil { + lang := ctx.Value("Language").(lang.Language) + code = lang.Code + } + return code +} + +// SaveFirstname updates the first name in the gdbm with the provided input. +func (h *Handlers) SaveFirstname(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") + } + + if len(input) > 0 { + firstName := string(input) + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_FIRST_NAME, []byte(firstName)) + if err != nil { + return res, nil + } + } + + return res, nil +} + +// SaveFamilyname updates the family name in the gdbm with the provided input. +func (h *Handlers) SaveFamilyname(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") + } + + if len(input) > 0 { + familyName := string(input) + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_FAMILY_NAME, []byte(familyName)) + if err != nil { + return res, nil + } + } else { + return res, fmt.Errorf("a family name cannot be less than one character") + } + + return res, nil +} + +// SaveYOB updates the Year of Birth(YOB) in the gdbm with the provided input. +func (h *Handlers) SaveYob(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") + } + + if len(input) == 4 { + yob := string(input) + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_YOB, []byte(yob)) + if err != nil { + return res, nil + } + } + + return res, nil +} + +// SaveLocation updates the location in the gdbm with the provided input. +func (h *Handlers) SaveLocation(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") + } + + if len(input) > 0 { + location := string(input) + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_LOCATION, []byte(location)) + if err != nil { + return res, nil + } + } + + return res, nil +} + +// SaveGender updates the gender in the gdbm with the provided input. +func (h *Handlers) SaveGender(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") + } + + if len(input) > 0 { + gender := string(input) + switch gender { + case "1": + gender = "Male" + case "2": + gender = "Female" + case "3": + gender = "Unspecified" + } + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_GENDER, []byte(gender)) + if err != nil { + return res, nil + } + } + + return res, nil +} + +// SaveOfferings updates the offerings(goods and services provided by the user) in the gdbm with the provided input. +func (h *Handlers) SaveOfferings(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") + } + + if len(input) > 0 { + offerings := string(input) + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_OFFERINGS, []byte(offerings)) + if err != nil { + return res, nil + } + } + + return res, nil +} + +// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data. +func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update") + + res.FlagReset = append(res.FlagReset, flag_allow_update) + return res, nil +} + +// 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) + return res, nil +} + +// 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") + } + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + res.Content = string(publicKey) + + return res, nil +} + +// Authorize attempts to unlock the next sequential nodes by verifying the provided PIN against the already set PIN. +// It sets the required flags that control the flow. +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") + + AccountPin, err := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_PIN) + + if err == nil { + if len(input) == 4 { + if bytes.Equal(input, AccountPin) { + if h.st.MatchFlag(flag_account_authorized, false) { + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + res.FlagSet = append(res.FlagSet, flag_allow_update, flag_account_authorized) + } else { + res.FlagSet = append(res.FlagSet, flag_allow_update) + res.FlagReset = append(res.FlagReset, flag_account_authorized) + } + } else { + res.FlagSet = append(res.FlagSet, flag_incorrect_pin) + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil + } + } + } else { + return res, nil + } + + return res, nil +} + +// ResetIncorrectPin resets the incorrect pin flag after a new PIN attempt. +func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_incorrect_pin, _ := h.flagManager.GetFlag("flag_incorrect_pin") + + res.FlagReset = append(res.FlagReset, flag_incorrect_pin) + return res, nil +} + +// CheckAccountStatus queries the API using the TrackingId and sets flags +// based on the account status +func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_account_success, _ := h.flagManager.GetFlag("flag_account_success") + flag_account_pending, _ := h.flagManager.GetFlag("flag_account_pending") + + sessionId, ok := ctx.Value("SessionId").(string) + if !ok { + return res, fmt.Errorf("missing session") + } + + trackingId, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_TRACKING_ID) + + status, err := h.accountService.CheckAccountStatus(string(trackingId)) + if err != nil { + fmt.Println("Error checking account status:", err) + return res, err + } + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_ACCOUNT_STATUS, []byte(status)) + if err != nil { + return res, nil + } + + if status == "SUCCESS" { + res.FlagSet = append(res.FlagSet, flag_account_success) + res.FlagReset = append(res.FlagReset, flag_account_pending) + } else { + res.FlagReset = append(res.FlagReset, flag_account_success) + res.FlagSet = append(res.FlagSet, flag_account_pending) + } + return res, nil +} + +// Quit displays the Thank you message and exits the menu +func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + res.Content = l.Get("Thank you for using Sarafu. Goodbye!") + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} + +// VerifyYob verifies the length of the given input +func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + var err error + + flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") + + date := string(input) + _, err = strconv.Atoi(date) + if err != nil { + // If conversion fails, input is not numeric + res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) + return res, nil + } + + if len(date) == 4 { + res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) + } else { + res.FlagSet = append(res.FlagSet, flag_incorrect_date_format) + } + + return res, nil +} + +// ResetIncorrectYob resets the incorrect date format flag after a new attempt +func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) { + var res resource.Result + + flag_incorrect_date_format, _ := h.flagManager.GetFlag("flag_incorrect_date_format") + + res.FlagReset = append(res.FlagReset, flag_incorrect_date_format) + return res, nil +} + +// CheckBalance retrieves the balance from the API using the "PublicKey" and sets +// the balance as the result content +func (h *Handlers) CheckBalance(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") + } + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + balance, err := h.accountService.CheckBalance(string(publicKey)) + if err != nil { + return res, nil + } + res.Content = balance + + return res, nil +} + +// ValidateRecipient validates that the given input is a valid phone number. +func (h *Handlers) ValidateRecipient(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") + } + + recipient := string(input) + + flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") + + if recipient != "0" { + // mimic invalid number check + if recipient == "000" { + res.FlagSet = append(res.FlagSet, flag_invalid_recipient) + res.Content = recipient + + return res, nil + } + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_RECIPIENT, []byte(recipient)) + if err != nil { + return res, nil + } + } + + return res, nil +} + +// TransactionReset resets the previous transaction data (Recipient and Amount) +// as well as the invalid flags +func (h *Handlers) TransactionReset(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_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient") + flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite") + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_AMOUNT, []byte("")) + if err != nil { + return res, nil + } + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_RECIPIENT, []byte("")) + if err != nil { + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_invalid_recipient, flag_invalid_recipient_with_invite) + + return res, nil +} + +// ResetTransactionAmount resets the transaction amount and invalid flag +func (h *Handlers) ResetTransactionAmount(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_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_AMOUNT, []byte("")) + if err != nil { + return res, nil + } + + res.FlagReset = append(res.FlagReset, flag_invalid_amount) + + return res, nil +} + +// MaxAmount gets the current balance from the API and sets it as +// the result content. +func (h *Handlers) MaxAmount(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") + } + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + balance, err := h.accountService.CheckBalance(string(publicKey)) + if err != nil { + return res, nil + } + + res.Content = balance + + return res, nil +} + +// ValidateAmount ensures that the given input is a valid amount and that +// it is not more than the current balance. +func (h *Handlers) ValidateAmount(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_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount") + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + amountStr := string(input) + + balanceStr, err := h.accountService.CheckBalance(string(publicKey)) + + if err != nil { + return res, err + } + res.Content = balanceStr + + // Parse the balance + balanceParts := strings.Split(balanceStr, " ") + if len(balanceParts) != 2 { + return res, fmt.Errorf("unexpected balance format: %s", balanceStr) + } + balanceValue, err := strconv.ParseFloat(balanceParts[0], 64) + if err != nil { + return res, fmt.Errorf("failed to parse balance: %v", err) + } + + // Extract numeric part from input + re := regexp.MustCompile(`^(\d+(\.\d+)?)\s*(?:CELO)?$`) + matches := re.FindStringSubmatch(strings.TrimSpace(amountStr)) + if len(matches) < 2 { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = amountStr + return res, nil + } + + inputAmount, err := strconv.ParseFloat(matches[1], 64) + if err != nil { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = amountStr + return res, nil + } + + if inputAmount > balanceValue { + res.FlagSet = append(res.FlagSet, flag_invalid_amount) + res.Content = amountStr + return res, nil + } + + res.Content = fmt.Sprintf("%.3f", inputAmount) // Format to 3 decimal places + + err = utils.WriteEntry(ctx, h.userdataStore, sessionId, utils.DATA_AMOUNT, []byte(amountStr)) + if err != nil { + return res, err + } + + return res, nil +} + +// GetRecipient returns the transaction recipient from the gdbm. +func (h *Handlers) GetRecipient(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") + } + + recipient, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_RECIPIENT) + + res.Content = string(recipient) + + return res, nil +} + +// GetSender retrieves the public key from the Gdbm Db +func (h *Handlers) GetSender(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") + } + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + res.Content = string(publicKey) + + return res, nil +} + +// GetAmount retrieves the amount from teh Gdbm Db +func (h *Handlers) GetAmount(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") + } + + amount, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_AMOUNT) + + res.Content = string(amount) + + return res, nil +} + +// QuickWithBalance retrieves the balance for a given public key from the custodial balance API endpoint before +// gracefully exiting the session. +func (h *Handlers) QuitWithBalance(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_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized") + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + balance, err := h.accountService.CheckBalance(string(publicKey)) + if err != nil { + return res, nil + } + res.Content = l.Get("Your account balance is %s", balance) + res.FlagReset = append(res.FlagReset, flag_account_authorized) + return res, nil +} + +// InitiateTransaction returns a confirmation and resets the transaction data +// on the gdbm store. +func (h *Handlers) InitiateTransaction(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") + } + + code := codeFromCtx(ctx) + l := gotext.NewLocale(translationDir, code) + l.AddDomain("default") + // TODO + // Use the amount, recipient and sender to call the API and initialize the transaction + + publicKey, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_PUBLIC_KEY) + + amount, _ := utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_AMOUNT) + + recipient, _ := utils.ReadEntry(ctx, h.userdataStore, 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)) + + account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized") + if err != nil { + return res, err + } + + res.FlagReset = append(res.FlagReset, account_authorized_flag) + return res, nil +} + +// GetProfileInfo retrieves and formats the profile information of a user from a Gdbm backed storage. +func (h *Handlers) GetProfileInfo(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") + } + + // Default value when an entry is not found + defaultValue := "Not Provided" + + // Helper function to handle nil byte slices and convert them to string + getEntryOrDefault := func(entry []byte, err error) string { + if err != nil || entry == nil { + return defaultValue + } + return string(entry) + } + + // Retrieve user data as strings with fallback to defaultValue + firstName := getEntryOrDefault(utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_FIRST_NAME)) + familyName := getEntryOrDefault(utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_FAMILY_NAME)) + yob := getEntryOrDefault(utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_YOB)) + gender := getEntryOrDefault(utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_GENDER)) + location := getEntryOrDefault(utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_LOCATION)) + offerings := getEntryOrDefault(utils.ReadEntry(ctx, h.userdataStore, sessionId, utils.DATA_OFFERINGS)) + + // Construct the full name + name := defaultValue + if familyName != defaultValue { + if firstName == defaultValue { + name = familyName + } else { + name = firstName + " " + familyName + } + } + + // Calculate age from year of birth + age := defaultValue + if yob != defaultValue { + if yobInt, err := strconv.Atoi(yob); err == nil { + age = strconv.Itoa(utils.CalculateAgeWithYOB(yobInt)) + } else { + return res, fmt.Errorf("invalid year of birth: %v", err) + } + } + + // Format the result + res.Content = fmt.Sprintf( + "Name: %s\nGender: %s\nAge: %s\nLocation: %s\nYou provide: %s\n", + name, gender, age, location, offerings, + ) + + return res, nil +} diff --git a/internal/handlers/ussd/menuhandler_test.go b/internal/handlers/ussd/menuhandler_test.go new file mode 100644 index 0000000..4f26e12 --- /dev/null +++ b/internal/handlers/ussd/menuhandler_test.go @@ -0,0 +1,171 @@ +package ussd + +import ( + "context" + "encoding/json" + "testing" + + "git.defalsify.org/vise.git/db" + "git.grassecon.net/urdt/ussd/internal/handlers/ussd/mocks" + "git.grassecon.net/urdt/ussd/internal/models" + "git.grassecon.net/urdt/ussd/internal/utils" + "github.com/alecthomas/assert/v2" + "github.com/stretchr/testify/mock" +) + +func TestCreateAccount_Success(t *testing.T) { + mockCreateAccountService := new(mocks.MockAccountService) + mockUserDataStore := new(mocks.MockDb) + + h := &Handlers{ + userdataStore: mockUserDataStore, + accountService: mockCreateAccountService, + } + ctx := context.WithValue(context.Background(), "SessionId", "test-session-12345") + k := utils.PackKey(utils.DATA_ACCOUNT_CREATED, []byte("test-session-12345")) + mockUserDataStore.On("SetPrefix", uint8(0x20)).Return(nil) + mockUserDataStore.On("SetSession", "test-session-12345").Return(nil) + mockUserDataStore.On("Get", ctx, k). + Return(nil, db.ErrNotFound{}) + + // Define expected account response after api call + expectedAccountResp := &models.AccountResponse{ + Ok: true, + Result: struct { + CustodialId json.Number `json:"custodialId"` + PublicKey string `json:"publicKey"` + TrackingId string `json:"trackingId"` + }{ + CustodialId: "12", + PublicKey: "0x8E0XSCSVA", + TrackingId: "d95a7e83-196c-4fd0-866fSGAGA", + }, + } + mockCreateAccountService.On("CreateAccount").Return(expectedAccountResp, nil) + + _, err := h.CreateAccount(ctx, "create_account", []byte("create_account")) + + // Assert results + assert.NoError(t, err) + assert.Equal(t, expectedAccountResp.Ok, true) +} + +func TestSaveFirstname(t *testing.T) { + // Create a mock database + mockDb := new(mocks.MockDb) + + // Create a Handlers instance with the mock database + h := &Handlers{ + userdataStore: mockDb, + } + + // Create a context with a session ID + ctx := context.WithValue(context.Background(), "SessionId", "test-session") + + tests := []struct { + name string + input []byte + expectError bool + setupMock func(*mocks.MockDb) + }{ + { + name: "Valid first name", + input: []byte("John"), + expectError: false, + setupMock: func(m *mocks.MockDb) { + m.On("SetPrefix", uint8(0x20)).Return(nil) + m.On("SetSession", "test-session").Return(nil) + m.On("Put", mock.Anything, mock.Anything, []byte("John")).Return(nil) + }, + }, + { + name: "Empty first name", + input: []byte{}, + expectError: false, // Note: The function doesn't return an error for empty input + setupMock: func(m *mocks.MockDb) {}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock expectations + tt.setupMock(mockDb) + + // Call the function + _, err := h.SaveFirstname(ctx, "", tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + mockDb.AssertExpectations(t) + } + + // Clear mock for the next test + mockDb.ExpectedCalls = nil + mockDb.Calls = nil + }) + } +} + +func TestSaveFamilyname(t *testing.T) { + // Create a mock database + mockDb := new(mocks.MockDb) + + // Create a Handlers instance with the mock database + h := &Handlers{ + userdataStore: mockDb, + } + + // Create a context with a session ID + ctx := context.WithValue(context.Background(), "SessionId", "test-session") + + tests := []struct { + name string + input []byte + expectError bool + setupMock func(*mocks.MockDb) + }{ + { + name: "Valid family name", + input: []byte("Smith"), + expectError: false, + setupMock: func(m *mocks.MockDb) { + m.On("SetPrefix", uint8(0x20)).Return(nil) + m.On("SetSession", "test-session").Return(nil) + m.On("Put", mock.Anything, mock.Anything, []byte("Smith")).Return(nil) + }, + }, + { + name: "Empty family name", + input: []byte{}, + expectError: true, + setupMock: func(m *mocks.MockDb) {}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Setup mock expectations + tt.setupMock(mockDb) + + // Call the function + _, err := h.SaveFamilyname(ctx, "", tt.input) + + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + mockDb.AssertExpectations(t) + } + + // Clear mock for the next test + mockDb.ExpectedCalls = nil + mockDb.Calls = nil + }) + } +} + +func testSavePin () { + +} \ No newline at end of file diff --git a/internal/handlers/ussd/mocks/dbmock.go b/internal/handlers/ussd/mocks/dbmock.go new file mode 100644 index 0000000..0b40eab --- /dev/null +++ b/internal/handlers/ussd/mocks/dbmock.go @@ -0,0 +1,59 @@ +package mocks + +import ( + "context" + + "git.defalsify.org/vise.git/lang" + "github.com/stretchr/testify/mock" +) + +type MockDb struct { + mock.Mock +} + +func (m *MockDb) SetPrefix(prefix uint8) { + m.Called(prefix) +} + +func (m *MockDb) Prefix() uint8 { + args := m.Called() + return args.Get(0).(uint8) +} + +func (m *MockDb) Safe() bool { + args := m.Called() + return args.Get(0).(bool) +} + +func (m *MockDb) SetLanguage(language *lang.Language) { + m.Called(language) +} + +func (m *MockDb) SetLock(uint8, bool) error { + args := m.Called() + return args.Error(0) +} + +func (m *MockDb) Connect(ctx context.Context, connectionStr string) error { + args := m.Called(ctx, connectionStr) + return args.Error(0) +} + +func (m *MockDb) SetSession(sessionId string) { + m.Called(sessionId) +} + +func (m *MockDb) Put(ctx context.Context, key, value []byte) error { + args := m.Called(ctx, key, value) + return args.Error(0) +} + +func (m *MockDb) Get(ctx context.Context, key []byte) ([]byte, error) { + args := m.Called(ctx, key) + return nil, args.Error(0) +} + +func (m *MockDb) Close() error { + args := m.Called(nil) + return args.Error(0) +} diff --git a/internal/handlers/ussd/mocks/servicemock.go b/internal/handlers/ussd/mocks/servicemock.go new file mode 100644 index 0000000..9fb6d3e --- /dev/null +++ b/internal/handlers/ussd/mocks/servicemock.go @@ -0,0 +1,26 @@ +package mocks + +import ( + "git.grassecon.net/urdt/ussd/internal/models" + "github.com/stretchr/testify/mock" +) + +// MockAccountService implements AccountServiceInterface for testing +type MockAccountService struct { + mock.Mock +} + +func (m *MockAccountService) CreateAccount() (*models.AccountResponse, error) { + args := m.Called() + return args.Get(0).(*models.AccountResponse), args.Error(1) +} + +func (m *MockAccountService) CheckBalance(publicKey string) (string, error) { + args := m.Called(publicKey) + return args.String(0), args.Error(1) +} + +func (m *MockAccountService) CheckAccountStatus(trackingId string) (string, error) { + args := m.Called(trackingId) + return args.String(0), args.Error(1) +} \ No newline at end of file diff --git a/internal/models/accountresponse.go b/internal/models/accountresponse.go new file mode 100644 index 0000000..1422a20 --- /dev/null +++ b/internal/models/accountresponse.go @@ -0,0 +1,15 @@ +package models + +import ( + "encoding/json" + +) + +type AccountResponse struct { + Ok bool `json:"ok"` + Result struct { + CustodialId json.Number `json:"custodialId"` + PublicKey string `json:"publicKey"` + TrackingId string `json:"trackingId"` + } `json:"result"` +} \ No newline at end of file diff --git a/internal/models/balanceresponse.go b/internal/models/balanceresponse.go new file mode 100644 index 0000000..57c8e5a --- /dev/null +++ b/internal/models/balanceresponse.go @@ -0,0 +1,12 @@ +package models + +import "encoding/json" + + +type BalanceResponse struct { + Ok bool `json:"ok"` + Result struct { + Balance string `json:"balance"` + Nonce json.Number `json:"nonce"` + } `json:"result"` +} diff --git a/internal/models/trackstatusresponse.go b/internal/models/trackstatusresponse.go new file mode 100644 index 0000000..6054281 --- /dev/null +++ b/internal/models/trackstatusresponse.go @@ -0,0 +1,20 @@ +package models + +import ( + "encoding/json" + "time" +) + + +type TrackStatusResponse struct { + Ok bool `json:"ok"` + 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"` + } + } `json:"result"` +} \ No newline at end of file diff --git a/internal/utils/account_utils.go b/internal/utils/account_utils.go new file mode 100644 index 0000000..b9fd1e3 --- /dev/null +++ b/internal/utils/account_utils.go @@ -0,0 +1,44 @@ +package utils + +import ( + "context" + "encoding/json" + + "git.defalsify.org/vise.git/db" +) + +type AccountFileHandler struct { + store db.Db +} + +func NewAccountFileHandler(store db.Db) *AccountFileHandler { + return &AccountFileHandler{ + store: store, + } +} + +func (afh *AccountFileHandler) ReadAccountData(ctx context.Context, sessionId string) (map[string]string, error) { + var accountData map[string]string + jsonData, err := ReadEntry(ctx, afh.store, sessionId, DATA_ACCOUNT) + if err != nil { + return nil,err + } + err = json.Unmarshal(jsonData, &accountData) + if err != nil { + return nil, err + } + return accountData, nil +} + +func (afh *AccountFileHandler) WriteAccountData(ctx context.Context, sessionId string, accountData map[string]string) error { + _, err := json.Marshal(accountData) + if err != nil { + return err + } + + return nil +} + +func (afh *AccountFileHandler) EnsureFileExists() error { + return nil +} diff --git a/internal/utils/age.go b/internal/utils/age.go new file mode 100644 index 0000000..6b040e7 --- /dev/null +++ b/internal/utils/age.go @@ -0,0 +1,35 @@ +package utils + +import "time" + +// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy +// It adjusts for cases where the current date is before the birthday in the current year. +func CalculateAge(birthdate, today time.Time) int { + today = today.In(birthdate.Location()) + ty, tm, td := today.Date() + today = time.Date(ty, tm, td, 0, 0, 0, 0, time.UTC) + by, bm, bd := birthdate.Date() + birthdate = time.Date(by, bm, bd, 0, 0, 0, 0, time.UTC) + if today.Before(birthdate) { + return 0 + } + age := ty - by + anniversary := birthdate.AddDate(age, 0, 0) + if anniversary.After(today) { + age-- + } + return age +} + +// CalculateAgeWithYOB calculates the age based on the given year of birth (YOB). +// It subtracts the YOB from the current year to determine the age. +// +// Parameters: +// yob: The year of birth as an integer. +// +// Returns: +// The calculated age as an integer. +func CalculateAgeWithYOB(yob int) int { + currentYear := time.Now().Year() + return currentYear - yob +} \ No newline at end of file diff --git a/internal/utils/db.go b/internal/utils/db.go new file mode 100644 index 0000000..5b128f6 --- /dev/null +++ b/internal/utils/db.go @@ -0,0 +1,58 @@ +package utils + +import ( + "context" + "encoding/binary" + + "git.defalsify.org/vise.git/db" +) + +type DataTyp uint16 + +const ( + DATA_ACCOUNT DataTyp = iota + DATA_ACCOUNT_CREATED + DATA_TRACKING_ID + DATA_PUBLIC_KEY + DATA_CUSTODIAL_ID + DATA_ACCOUNT_PIN + DATA_ACCOUNT_STATUS + DATA_FIRST_NAME + DATA_FAMILY_NAME + DATA_YOB + DATA_LOCATION + DATA_GENDER + DATA_OFFERINGS + DATA_RECIPIENT + DATA_AMOUNT +) + +func typToBytes(typ DataTyp) []byte { + var b [2]byte + binary.BigEndian.PutUint16(b[:], uint16(typ)) + return b[:] +} + +func PackKey(typ DataTyp, data []byte) []byte { + v := typToBytes(typ) + return append(v, data...) +} + +func ReadEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp) ([]byte, error) { + + store.SetPrefix(db.DATATYPE_USERDATA) + store.SetSession(sessionId) + k := PackKey(typ, []byte(sessionId)) + b, err := store.Get(ctx, k) + if err != nil { + return nil, err + } + return b, nil +} + +func WriteEntry(ctx context.Context, store db.Db, sessionId string, typ DataTyp, value []byte) error { + store.SetPrefix(db.DATATYPE_USERDATA) + store.SetSession(sessionId) + k := PackKey(typ, []byte(sessionId)) + return store.Put(ctx, k, value) +} diff --git a/services/registration/Makefile b/services/registration/Makefile new file mode 100644 index 0000000..af8b8c1 --- /dev/null +++ b/services/registration/Makefile @@ -0,0 +1,17 @@ +# Variables to match files in the current directory +INPUTS = $(wildcard ./*.vis) +TXTS = $(wildcard ./*.txt.orig) + +# Rule to build .bin files from .vis files +%.vis: + go run ../../go-vise/dev/asm/main.go -f pp.csv $(basename $@).vis > $(basename $@).bin + @echo "Built $(basename $@).bin from $(basename $@).vis" + +# Rule to copy .orig files to .txt +%.txt.orig: + cp -v $(basename $@).orig $(basename $@) + @echo "Copied $(basename $@).orig to $(basename $@)" + +# 'all' target depends on all .vis and .txt.orig files +all: $(INPUTS) $(TXTS) + @echo "Running all: $(INPUTS) $(TXTS)" diff --git a/services/registration/account_creation b/services/registration/account_creation new file mode 100644 index 0000000..e9463a6 --- /dev/null +++ b/services/registration/account_creation @@ -0,0 +1 @@ +Your account is being created... \ No newline at end of file diff --git a/services/registration/account_creation.vis b/services/registration/account_creation.vis new file mode 100644 index 0000000..f4f326b --- /dev/null +++ b/services/registration/account_creation.vis @@ -0,0 +1,4 @@ +RELOAD verify_pin +CATCH create_pin_mismatch flag_pin_mismatch 1 +LOAD quit 0 +HALT diff --git a/services/registration/account_creation_failed b/services/registration/account_creation_failed new file mode 100644 index 0000000..0df00db --- /dev/null +++ b/services/registration/account_creation_failed @@ -0,0 +1 @@ +Your account creation request failed. Please try again later. \ No newline at end of file diff --git a/services/registration/account_creation_failed.vis b/services/registration/account_creation_failed.vis new file mode 100644 index 0000000..b62b797 --- /dev/null +++ b/services/registration/account_creation_failed.vis @@ -0,0 +1,3 @@ +MOUT quit 9 +HALT +INCMP quit 9 diff --git a/services/registration/account_creation_failed_swa b/services/registration/account_creation_failed_swa new file mode 100644 index 0000000..6f0ac7b --- /dev/null +++ b/services/registration/account_creation_failed_swa @@ -0,0 +1 @@ +Ombi lako la kusajiliwa haliwezi kukamilishwa. Tafadhali jaribu tena baadaye. \ No newline at end of file diff --git a/services/registration/account_creation_swa b/services/registration/account_creation_swa new file mode 100644 index 0000000..6e5b1e1 --- /dev/null +++ b/services/registration/account_creation_swa @@ -0,0 +1 @@ +Akaunti yako inatengenezwa... \ No newline at end of file diff --git a/services/registration/account_menu b/services/registration/account_menu new file mode 100644 index 0000000..7aa9fe9 --- /dev/null +++ b/services/registration/account_menu @@ -0,0 +1 @@ +My Account \ No newline at end of file diff --git a/services/registration/account_menu_swa b/services/registration/account_menu_swa new file mode 100644 index 0000000..c77102f --- /dev/null +++ b/services/registration/account_menu_swa @@ -0,0 +1 @@ +Akaunti yangu \ No newline at end of file diff --git a/services/registration/account_pending b/services/registration/account_pending new file mode 100644 index 0000000..4eadf25 --- /dev/null +++ b/services/registration/account_pending @@ -0,0 +1 @@ +Your account is still being created. \ No newline at end of file diff --git a/services/registration/account_pending.vis b/services/registration/account_pending.vis new file mode 100644 index 0000000..d122613 --- /dev/null +++ b/services/registration/account_pending.vis @@ -0,0 +1,3 @@ +RELOAD check_account_status +CATCH main flag_account_success 1 +HALT diff --git a/services/registration/account_pending_swa b/services/registration/account_pending_swa new file mode 100644 index 0000000..2e514b5 --- /dev/null +++ b/services/registration/account_pending_swa @@ -0,0 +1 @@ +Akaunti yako bado inatengenezwa \ No newline at end of file diff --git a/services/registration/address b/services/registration/address new file mode 100644 index 0000000..6353876 --- /dev/null +++ b/services/registration/address @@ -0,0 +1 @@ +Address: {{.check_identifier}} \ No newline at end of file diff --git a/services/registration/address.vis b/services/registration/address.vis new file mode 100644 index 0000000..f3ba04a --- /dev/null +++ b/services/registration/address.vis @@ -0,0 +1,6 @@ +LOAD check_identifier 0 +RELOAD check_identifier +MAP check_identifier +MOUT quit 9 +HALT +INCMP quit 9 diff --git a/services/registration/amount b/services/registration/amount new file mode 100644 index 0000000..9142aba --- /dev/null +++ b/services/registration/amount @@ -0,0 +1,2 @@ +Maximum amount: {{.max_amount}} +Enter amount: \ No newline at end of file diff --git a/services/registration/amount.vis b/services/registration/amount.vis new file mode 100644 index 0000000..9b2970a --- /dev/null +++ b/services/registration/amount.vis @@ -0,0 +1,12 @@ +LOAD reset_transaction_amount 0 +LOAD max_amount 10 +MAP max_amount +MOUT back 0 +HALT +LOAD validate_amount 64 +RELOAD validate_amount +CATCH invalid_amount flag_invalid_amount 1 +INCMP _ 0 +LOAD get_recipient 12 +LOAD get_sender 64 +INCMP transaction_pin * diff --git a/services/registration/amount_swa b/services/registration/amount_swa new file mode 100644 index 0000000..0c8cf01 --- /dev/null +++ b/services/registration/amount_swa @@ -0,0 +1,2 @@ +Kiwango cha juu: {{.max_amount}} +Weka kiwango: \ No newline at end of file diff --git a/services/registration/back_menu b/services/registration/back_menu new file mode 100644 index 0000000..2278c97 --- /dev/null +++ b/services/registration/back_menu @@ -0,0 +1 @@ +Back \ No newline at end of file diff --git a/services/registration/back_menu_swa b/services/registration/back_menu_swa new file mode 100644 index 0000000..751fb22 --- /dev/null +++ b/services/registration/back_menu_swa @@ -0,0 +1 @@ +Rudi diff --git a/services/registration/balances b/services/registration/balances new file mode 100644 index 0000000..697be51 --- /dev/null +++ b/services/registration/balances @@ -0,0 +1 @@ +Balances: diff --git a/services/registration/balances.vis b/services/registration/balances.vis new file mode 100644 index 0000000..dd14501 --- /dev/null +++ b/services/registration/balances.vis @@ -0,0 +1,8 @@ +LOAD reset_account_authorized 0 +MOUT my_balance 1 +MOUT community_balance 2 +MOUT back 0 +HALT +INCMP _ 0 +INCMP my_balance 1 +INCMP community_balance 2 diff --git a/services/registration/balances_swa b/services/registration/balances_swa new file mode 100644 index 0000000..61a05a6 --- /dev/null +++ b/services/registration/balances_swa @@ -0,0 +1 @@ +Salio \ No newline at end of file diff --git a/services/registration/change_language_menu b/services/registration/change_language_menu new file mode 100644 index 0000000..7175fce --- /dev/null +++ b/services/registration/change_language_menu @@ -0,0 +1 @@ +Change language \ No newline at end of file diff --git a/services/registration/change_language_menu_swa b/services/registration/change_language_menu_swa new file mode 100644 index 0000000..02c46e1 --- /dev/null +++ b/services/registration/change_language_menu_swa @@ -0,0 +1 @@ +Badili lugha diff --git a/services/registration/change_pin_menu b/services/registration/change_pin_menu new file mode 100644 index 0000000..3ef432e --- /dev/null +++ b/services/registration/change_pin_menu @@ -0,0 +1 @@ +Change PIN diff --git a/services/registration/change_pin_menu_swa b/services/registration/change_pin_menu_swa new file mode 100644 index 0000000..cd48f9d --- /dev/null +++ b/services/registration/change_pin_menu_swa @@ -0,0 +1 @@ +Badili PIN diff --git a/services/registration/check_balance_menu b/services/registration/check_balance_menu new file mode 100644 index 0000000..253f368 --- /dev/null +++ b/services/registration/check_balance_menu @@ -0,0 +1 @@ +Check balances \ No newline at end of file diff --git a/services/registration/check_balance_menu_swa b/services/registration/check_balance_menu_swa new file mode 100644 index 0000000..4fe14f2 --- /dev/null +++ b/services/registration/check_balance_menu_swa @@ -0,0 +1 @@ +Angalia salio \ No newline at end of file diff --git a/services/registration/check_statement_menu b/services/registration/check_statement_menu new file mode 100644 index 0000000..f29634b --- /dev/null +++ b/services/registration/check_statement_menu @@ -0,0 +1 @@ +Check statement diff --git a/services/registration/check_statement_menu_swa b/services/registration/check_statement_menu_swa new file mode 100644 index 0000000..b8a338d --- /dev/null +++ b/services/registration/check_statement_menu_swa @@ -0,0 +1 @@ +Taarifa ya matumizi \ No newline at end of file diff --git a/services/registration/comminity_balance_swa b/services/registration/comminity_balance_swa new file mode 100644 index 0000000..726fc99 --- /dev/null +++ b/services/registration/comminity_balance_swa @@ -0,0 +1 @@ +Salio la kikundi \ No newline at end of file diff --git a/services/registration/community_balance b/services/registration/community_balance new file mode 100644 index 0000000..e79d40a --- /dev/null +++ b/services/registration/community_balance @@ -0,0 +1,2 @@ +Your community balance is: 0.00SRF + diff --git a/services/registration/community_balance.vis b/services/registration/community_balance.vis new file mode 100644 index 0000000..151c6d8 --- /dev/null +++ b/services/registration/community_balance.vis @@ -0,0 +1,5 @@ +LOAD reset_incorrect 0 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH pin_entry flag_account_authorized 0 +LOAD quit_with_balance 0 +HALT diff --git a/services/registration/community_balance_menu b/services/registration/community_balance_menu new file mode 100644 index 0000000..3833589 --- /dev/null +++ b/services/registration/community_balance_menu @@ -0,0 +1 @@ +Community balance \ No newline at end of file diff --git a/services/registration/community_balance_menu_swa b/services/registration/community_balance_menu_swa new file mode 100644 index 0000000..726fc99 --- /dev/null +++ b/services/registration/community_balance_menu_swa @@ -0,0 +1 @@ +Salio la kikundi \ No newline at end of file diff --git a/services/registration/confirm_create_pin b/services/registration/confirm_create_pin new file mode 100644 index 0000000..e4632ad --- /dev/null +++ b/services/registration/confirm_create_pin @@ -0,0 +1 @@ +Enter your four number PIN again: \ No newline at end of file diff --git a/services/registration/confirm_create_pin.vis b/services/registration/confirm_create_pin.vis new file mode 100644 index 0000000..1235916 --- /dev/null +++ b/services/registration/confirm_create_pin.vis @@ -0,0 +1,4 @@ +LOAD save_pin 0 +HALT +LOAD verify_pin 8 +INCMP account_creation * diff --git a/services/registration/confirm_create_pin_swa b/services/registration/confirm_create_pin_swa new file mode 100644 index 0000000..f697854 --- /dev/null +++ b/services/registration/confirm_create_pin_swa @@ -0,0 +1 @@ +Weka PIN yako tena: \ No newline at end of file diff --git a/services/registration/create_pin b/services/registration/create_pin new file mode 100644 index 0000000..f8836f5 --- /dev/null +++ b/services/registration/create_pin @@ -0,0 +1 @@ +Please enter a new four number PIN for your account: \ No newline at end of file diff --git a/services/registration/create_pin.vis b/services/registration/create_pin.vis new file mode 100644 index 0000000..e0e330f --- /dev/null +++ b/services/registration/create_pin.vis @@ -0,0 +1,9 @@ +LOAD create_account 0 +CATCH account_creation_failed flag_account_creation_failed 1 +MOUT exit 0 +HALT +LOAD save_pin 0 +RELOAD save_pin +CATCH . flag_incorrect_pin 1 +INCMP quit 0 +INCMP confirm_create_pin * diff --git a/services/registration/create_pin_mismatch b/services/registration/create_pin_mismatch new file mode 100644 index 0000000..e75068c --- /dev/null +++ b/services/registration/create_pin_mismatch @@ -0,0 +1 @@ +The PIN is not a match. Try again \ No newline at end of file diff --git a/services/registration/create_pin_mismatch.vis b/services/registration/create_pin_mismatch.vis new file mode 100644 index 0000000..91793b5 --- /dev/null +++ b/services/registration/create_pin_mismatch.vis @@ -0,0 +1,5 @@ +MOUT retry 1 +MOUT quit 9 +HALT +INCMP confirm_create_pin 1 +INCMP quit 9 diff --git a/services/registration/create_pin_mismatch_swa b/services/registration/create_pin_mismatch_swa new file mode 100644 index 0000000..a1d7b6d --- /dev/null +++ b/services/registration/create_pin_mismatch_swa @@ -0,0 +1 @@ +PIN uliyoweka haifanani. Jaribu tena \ No newline at end of file diff --git a/services/registration/create_pin_swa b/services/registration/create_pin_swa new file mode 100644 index 0000000..1fdd972 --- /dev/null +++ b/services/registration/create_pin_swa @@ -0,0 +1 @@ +Tafadhali weka PIN mpya yenye nambari nne kwa akaunti yako: \ No newline at end of file diff --git a/services/registration/display_profile_info b/services/registration/display_profile_info new file mode 100644 index 0000000..669c6c6 --- /dev/null +++ b/services/registration/display_profile_info @@ -0,0 +1,5 @@ +Wasifu wangu +Name: Not provided +Gender: Not provided +Age: Not provided +Location: Not provided \ No newline at end of file diff --git a/services/registration/display_profile_info.vis b/services/registration/display_profile_info.vis new file mode 100644 index 0000000..3790a08 --- /dev/null +++ b/services/registration/display_profile_info.vis @@ -0,0 +1,3 @@ +MOUT back 0 +HALT +INCMP _ 0 diff --git a/services/registration/display_profile_info_swa b/services/registration/display_profile_info_swa new file mode 100644 index 0000000..e69de29 diff --git a/services/registration/edit_gender_menu b/services/registration/edit_gender_menu new file mode 100644 index 0000000..8946918 --- /dev/null +++ b/services/registration/edit_gender_menu @@ -0,0 +1 @@ +Edit gender \ No newline at end of file diff --git a/services/registration/edit_gender_menu_swa b/services/registration/edit_gender_menu_swa new file mode 100644 index 0000000..6d31ea8 --- /dev/null +++ b/services/registration/edit_gender_menu_swa @@ -0,0 +1 @@ +Weka jinsia \ No newline at end of file diff --git a/services/registration/edit_location_menu b/services/registration/edit_location_menu new file mode 100644 index 0000000..39ff1b7 --- /dev/null +++ b/services/registration/edit_location_menu @@ -0,0 +1 @@ +Edit location \ No newline at end of file diff --git a/services/registration/edit_location_menu_swa b/services/registration/edit_location_menu_swa new file mode 100644 index 0000000..a2a0e59 --- /dev/null +++ b/services/registration/edit_location_menu_swa @@ -0,0 +1 @@ +Weka eneo \ No newline at end of file diff --git a/services/registration/edit_name_menu b/services/registration/edit_name_menu new file mode 100644 index 0000000..63d97b9 --- /dev/null +++ b/services/registration/edit_name_menu @@ -0,0 +1 @@ +Edit name diff --git a/services/registration/edit_name_menu_swa b/services/registration/edit_name_menu_swa new file mode 100644 index 0000000..c50424f --- /dev/null +++ b/services/registration/edit_name_menu_swa @@ -0,0 +1 @@ +Weka jina diff --git a/services/registration/edit_offerings_menu b/services/registration/edit_offerings_menu new file mode 100644 index 0000000..74563b2 --- /dev/null +++ b/services/registration/edit_offerings_menu @@ -0,0 +1 @@ +Edit offerings diff --git a/services/registration/edit_offerings_menu_swa b/services/registration/edit_offerings_menu_swa new file mode 100644 index 0000000..f37e125 --- /dev/null +++ b/services/registration/edit_offerings_menu_swa @@ -0,0 +1 @@ +Weka unachouza \ No newline at end of file diff --git a/services/registration/edit_profile b/services/registration/edit_profile new file mode 100644 index 0000000..2c808e6 --- /dev/null +++ b/services/registration/edit_profile @@ -0,0 +1 @@ +My profile \ No newline at end of file diff --git a/services/registration/edit_profile.vis b/services/registration/edit_profile.vis new file mode 100644 index 0000000..566b827 --- /dev/null +++ b/services/registration/edit_profile.vis @@ -0,0 +1,20 @@ +LOAD reset_account_authorized 16 +LOAD reset_allow_update 0 +RELOAD reset_allow_update +MOUT edit_name 1 +MOUT edit_gender 2 +MOUT edit_yob 3 +MOUT edit_location 4 +MOUT edit_offerings 5 +MOUT view 6 +MOUT back 0 +HALT +INCMP _ 0 +LOAD set_reset_single_edit 0 +RELOAD set_reset_single_edit +INCMP enter_name 1 +INCMP select_gender 2 +INCMP enter_yob 3 +INCMP enter_location 4 +INCMP enter_offerings 5 +INCMP view_profile 6 diff --git a/services/registration/edit_profile_swa b/services/registration/edit_profile_swa new file mode 100644 index 0000000..8a12b7d --- /dev/null +++ b/services/registration/edit_profile_swa @@ -0,0 +1 @@ +Wasifu wangu \ No newline at end of file diff --git a/services/registration/edit_yob_menu b/services/registration/edit_yob_menu new file mode 100644 index 0000000..3451781 --- /dev/null +++ b/services/registration/edit_yob_menu @@ -0,0 +1 @@ +Edit year of birth \ No newline at end of file diff --git a/services/registration/edit_yob_menu_swa b/services/registration/edit_yob_menu_swa new file mode 100644 index 0000000..9bb272a --- /dev/null +++ b/services/registration/edit_yob_menu_swa @@ -0,0 +1 @@ +Weka mwaka wa kuzaliwa \ No newline at end of file diff --git a/services/registration/enter_familyname b/services/registration/enter_familyname new file mode 100644 index 0000000..15ffb07 --- /dev/null +++ b/services/registration/enter_familyname @@ -0,0 +1 @@ +Enter family name: diff --git a/services/registration/enter_familyname.vis b/services/registration/enter_familyname.vis new file mode 100644 index 0000000..93def9b --- /dev/null +++ b/services/registration/enter_familyname.vis @@ -0,0 +1,5 @@ +LOAD save_firstname 0 +MOUT back 0 +HALT +INCMP _ 0 +INCMP select_gender * diff --git a/services/registration/enter_familyname_swa b/services/registration/enter_familyname_swa new file mode 100644 index 0000000..e69de29 diff --git a/services/registration/enter_location b/services/registration/enter_location new file mode 100644 index 0000000..da0e148 --- /dev/null +++ b/services/registration/enter_location @@ -0,0 +1 @@ +Enter your location: diff --git a/services/registration/enter_location.vis b/services/registration/enter_location.vis new file mode 100644 index 0000000..00bed3d --- /dev/null +++ b/services/registration/enter_location.vis @@ -0,0 +1,9 @@ +CATCH incorrect_date_format flag_incorrect_date_format 1 +LOAD save_yob 0 +CATCH update_success flag_allow_update 1 +MOUT back 0 +HALT +INCMP _ 0 +LOAD save_location 0 +CATCH pin_entry flag_single_edit 1 +INCMP enter_offerings * diff --git a/services/registration/enter_location_swa b/services/registration/enter_location_swa new file mode 100644 index 0000000..41682a2 --- /dev/null +++ b/services/registration/enter_location_swa @@ -0,0 +1 @@ +Weka eneo: \ No newline at end of file diff --git a/services/registration/enter_name b/services/registration/enter_name new file mode 100644 index 0000000..c6851cf --- /dev/null +++ b/services/registration/enter_name @@ -0,0 +1 @@ +Enter your first names: \ No newline at end of file diff --git a/services/registration/enter_name.vis b/services/registration/enter_name.vis new file mode 100644 index 0000000..4126f07 --- /dev/null +++ b/services/registration/enter_name.vis @@ -0,0 +1,4 @@ +MOUT back 0 +HALT +INCMP _ 0 +INCMP enter_familyname * diff --git a/services/registration/enter_name_swa b/services/registration/enter_name_swa new file mode 100644 index 0000000..db04d35 --- /dev/null +++ b/services/registration/enter_name_swa @@ -0,0 +1 @@ +Weka majina yako ya kwanza: diff --git a/services/registration/enter_offerings b/services/registration/enter_offerings new file mode 100644 index 0000000..a9333ba --- /dev/null +++ b/services/registration/enter_offerings @@ -0,0 +1 @@ +Enter the services or goods you offer: \ No newline at end of file diff --git a/services/registration/enter_offerings.vis b/services/registration/enter_offerings.vis new file mode 100644 index 0000000..e590321 --- /dev/null +++ b/services/registration/enter_offerings.vis @@ -0,0 +1,8 @@ +LOAD save_location 0 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH update_success flag_allow_update 1 +MOUT back 0 +HALT +LOAD save_offerings 0 +INCMP _ 0 +INCMP pin_entry * diff --git a/services/registration/enter_offerings_swa b/services/registration/enter_offerings_swa new file mode 100644 index 0000000..f37e125 --- /dev/null +++ b/services/registration/enter_offerings_swa @@ -0,0 +1 @@ +Weka unachouza \ No newline at end of file diff --git a/services/registration/enter_pin b/services/registration/enter_pin new file mode 100644 index 0000000..cbb44ca --- /dev/null +++ b/services/registration/enter_pin @@ -0,0 +1 @@ +Please enter your PIN: \ No newline at end of file diff --git a/services/registration/enter_pin.vis b/services/registration/enter_pin.vis new file mode 100644 index 0000000..1217074 --- /dev/null +++ b/services/registration/enter_pin.vis @@ -0,0 +1,4 @@ +MOUT back 0 +HALT +INCMP _ 0 +INCMP display_profile_info * diff --git a/services/registration/enter_pin_swa b/services/registration/enter_pin_swa new file mode 100644 index 0000000..bb30cfd --- /dev/null +++ b/services/registration/enter_pin_swa @@ -0,0 +1 @@ +Weka PIN yako \ No newline at end of file diff --git a/services/registration/enter_yob b/services/registration/enter_yob new file mode 100644 index 0000000..54e039e --- /dev/null +++ b/services/registration/enter_yob @@ -0,0 +1 @@ +Enter your year of birth \ No newline at end of file diff --git a/services/registration/enter_yob.vis b/services/registration/enter_yob.vis new file mode 100644 index 0000000..3b27846 --- /dev/null +++ b/services/registration/enter_yob.vis @@ -0,0 +1,9 @@ +LOAD save_gender 0 +CATCH update_success flag_allow_update 1 +MOUT back 0 +HALT +INCMP _ 0 +LOAD verify_yob 8 +LOAD save_yob 0 +CATCH pin_entry flag_single_edit 1 +INCMP enter_location * diff --git a/services/registration/enter_yob_swa b/services/registration/enter_yob_swa new file mode 100644 index 0000000..9bb272a --- /dev/null +++ b/services/registration/enter_yob_swa @@ -0,0 +1 @@ +Weka mwaka wa kuzaliwa \ No newline at end of file diff --git a/services/registration/exit_menu b/services/registration/exit_menu new file mode 100644 index 0000000..1105a55 --- /dev/null +++ b/services/registration/exit_menu @@ -0,0 +1 @@ +Exit \ No newline at end of file diff --git a/services/registration/exit_menu_swa b/services/registration/exit_menu_swa new file mode 100644 index 0000000..474f1ff --- /dev/null +++ b/services/registration/exit_menu_swa @@ -0,0 +1 @@ +Ondoka \ No newline at end of file diff --git a/services/registration/female_menu b/services/registration/female_menu new file mode 100644 index 0000000..b26600f --- /dev/null +++ b/services/registration/female_menu @@ -0,0 +1 @@ +Female diff --git a/services/registration/female_menu_swa b/services/registration/female_menu_swa new file mode 100644 index 0000000..0506300 --- /dev/null +++ b/services/registration/female_menu_swa @@ -0,0 +1 @@ +Mwanamke \ No newline at end of file diff --git a/services/registration/guard_pin_menu b/services/registration/guard_pin_menu new file mode 100644 index 0000000..63ff8dd --- /dev/null +++ b/services/registration/guard_pin_menu @@ -0,0 +1 @@ +Guard my PIN diff --git a/services/registration/guard_pin_menu_swa b/services/registration/guard_pin_menu_swa new file mode 100644 index 0000000..e6f30d4 --- /dev/null +++ b/services/registration/guard_pin_menu_swa @@ -0,0 +1 @@ +Linda PIN yangu diff --git a/services/registration/help_menu b/services/registration/help_menu new file mode 100644 index 0000000..0c64ced --- /dev/null +++ b/services/registration/help_menu @@ -0,0 +1 @@ +Help \ No newline at end of file diff --git a/services/registration/help_menu_swa b/services/registration/help_menu_swa new file mode 100644 index 0000000..393e0c8 --- /dev/null +++ b/services/registration/help_menu_swa @@ -0,0 +1 @@ +Usaidizi \ No newline at end of file diff --git a/services/registration/incorrect_date_format b/services/registration/incorrect_date_format new file mode 100644 index 0000000..308b74c --- /dev/null +++ b/services/registration/incorrect_date_format @@ -0,0 +1,2 @@ +The year of birth you entered is invalid. +Please try again. diff --git a/services/registration/incorrect_date_format.vis b/services/registration/incorrect_date_format.vis new file mode 100644 index 0000000..e94db5d --- /dev/null +++ b/services/registration/incorrect_date_format.vis @@ -0,0 +1,6 @@ +LOAD reset_incorrect_date_format 8 +MOUT retry 1 +MOUT quit 9 +HALT +INCMP enter_yob 1 +INCMP quit 9 diff --git a/services/registration/incorrect_date_format_swa b/services/registration/incorrect_date_format_swa new file mode 100644 index 0000000..bd85f21 --- /dev/null +++ b/services/registration/incorrect_date_format_swa @@ -0,0 +1,2 @@ +Mwaka wa kuzaliwa ulioweka sio sahihi. +Tafadhali jaribu tena. \ No newline at end of file diff --git a/services/registration/incorrect_pin b/services/registration/incorrect_pin new file mode 100644 index 0000000..2bb04e6 --- /dev/null +++ b/services/registration/incorrect_pin @@ -0,0 +1 @@ +Incorrect pin \ No newline at end of file diff --git a/services/registration/incorrect_pin.vis b/services/registration/incorrect_pin.vis new file mode 100644 index 0000000..844f3d6 --- /dev/null +++ b/services/registration/incorrect_pin.vis @@ -0,0 +1,7 @@ +LOAD reset_incorrect 0 +RELOAD reset_incorrect +MOUT retry 1 +MOUT quit 9 +HALT +INCMP _ 1 +INCMP quit 9 diff --git a/services/registration/incorrect_pin_swa b/services/registration/incorrect_pin_swa new file mode 100644 index 0000000..34a0b28 --- /dev/null +++ b/services/registration/incorrect_pin_swa @@ -0,0 +1 @@ +PIN ulioeka sio sahihi \ No newline at end of file diff --git a/services/registration/invalid_amount b/services/registration/invalid_amount new file mode 100644 index 0000000..c4bbe3f --- /dev/null +++ b/services/registration/invalid_amount @@ -0,0 +1 @@ +Amount {{.validate_amount}} is invalid, please try again: \ No newline at end of file diff --git a/services/registration/invalid_amount.vis b/services/registration/invalid_amount.vis new file mode 100644 index 0000000..d5b5f03 --- /dev/null +++ b/services/registration/invalid_amount.vis @@ -0,0 +1,7 @@ +MAP validate_amount +RELOAD reset_transaction_amount +MOUT retry 1 +MOUT quit 9 +HALT +INCMP _ 1 +INCMP quit 9 diff --git a/services/registration/invalid_amount_swa b/services/registration/invalid_amount_swa new file mode 100644 index 0000000..836d7b2 --- /dev/null +++ b/services/registration/invalid_amount_swa @@ -0,0 +1 @@ +Kiwango {{.validate_amount}} sio sahihi, tafadhali weka tena: \ No newline at end of file diff --git a/services/registration/invalid_recipient b/services/registration/invalid_recipient new file mode 100644 index 0000000..0be78bd --- /dev/null +++ b/services/registration/invalid_recipient @@ -0,0 +1 @@ +{{.validate_recipient}} is not registered or invalid, please try again: \ No newline at end of file diff --git a/services/registration/invalid_recipient.vis b/services/registration/invalid_recipient.vis new file mode 100644 index 0000000..09efdde --- /dev/null +++ b/services/registration/invalid_recipient.vis @@ -0,0 +1,7 @@ +MAP validate_recipient +RELOAD transaction_reset +MOUT retry 1 +MOUT quit 9 +HALT +INCMP _ 1 +INCMP quit 9 diff --git a/services/registration/invalid_recipient_swa b/services/registration/invalid_recipient_swa new file mode 100644 index 0000000..39e7804 --- /dev/null +++ b/services/registration/invalid_recipient_swa @@ -0,0 +1 @@ +{{.validate_recipient}} haijasajiliwa au sio sahihi, tafadhali weka tena: \ No newline at end of file diff --git a/services/registration/list_offering b/services/registration/list_offering new file mode 100644 index 0000000..e69de29 diff --git a/services/registration/list_offering.vis b/services/registration/list_offering.vis new file mode 100644 index 0000000..e69de29 diff --git a/services/registration/locale/swa/default.po b/services/registration/locale/swa/default.po new file mode 100644 index 0000000..0a63d07 --- /dev/null +++ b/services/registration/locale/swa/default.po @@ -0,0 +1,8 @@ +msgid "Your account balance is %s" +msgstr "Salio lako ni %s" + +msgid "Your request has been sent. %s will receive %s from %s." +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!" diff --git a/services/registration/main b/services/registration/main new file mode 100644 index 0000000..bf15ea5 --- /dev/null +++ b/services/registration/main @@ -0,0 +1 @@ +Balance: {{.check_balance}} diff --git a/services/registration/main.vis b/services/registration/main.vis new file mode 100644 index 0000000..ede8296 --- /dev/null +++ b/services/registration/main.vis @@ -0,0 +1,15 @@ +LOAD check_balance 64 +RELOAD check_balance +MAP check_balance +MOUT send 1 +MOUT vouchers 2 +MOUT account 3 +MOUT help 4 +MOUT quit 9 +HALT +INCMP send 1 +INCMP quit 2 +INCMP my_account 3 +INCMP quit 4 +INCMP quit 9 +INCMP . * diff --git a/services/registration/main_swa b/services/registration/main_swa new file mode 100644 index 0000000..b72abf0 --- /dev/null +++ b/services/registration/main_swa @@ -0,0 +1 @@ +Salio: {{.check_balance}} diff --git a/services/registration/male_menu b/services/registration/male_menu new file mode 100644 index 0000000..183883f --- /dev/null +++ b/services/registration/male_menu @@ -0,0 +1 @@ +Male \ No newline at end of file diff --git a/services/registration/male_menu_swa b/services/registration/male_menu_swa new file mode 100644 index 0000000..7afdee9 --- /dev/null +++ b/services/registration/male_menu_swa @@ -0,0 +1 @@ +Mwanaume \ No newline at end of file diff --git a/services/registration/my_account b/services/registration/my_account new file mode 100644 index 0000000..7aa9fe9 --- /dev/null +++ b/services/registration/my_account @@ -0,0 +1 @@ +My Account \ No newline at end of file diff --git a/services/registration/my_account.vis b/services/registration/my_account.vis new file mode 100644 index 0000000..345b1a0 --- /dev/null +++ b/services/registration/my_account.vis @@ -0,0 +1,14 @@ +LOAD reset_allow_update 0 +MOUT profile 1 +MOUT change_language 2 +MOUT check_balance 3 +MOUT check_statement 4 +MOUT pin_options 5 +MOUT my_address 6 +MOUT back 0 +HALT +INCMP _ 0 +INCMP edit_profile 1 +INCMP balances 3 +INCMP pin_management 5 +INCMP address 6 diff --git a/services/registration/my_account_swa b/services/registration/my_account_swa new file mode 100644 index 0000000..c77102f --- /dev/null +++ b/services/registration/my_account_swa @@ -0,0 +1 @@ +Akaunti yangu \ No newline at end of file diff --git a/services/registration/my_address_menu b/services/registration/my_address_menu new file mode 100644 index 0000000..5c13a7d --- /dev/null +++ b/services/registration/my_address_menu @@ -0,0 +1 @@ +My Address \ No newline at end of file diff --git a/services/registration/my_address_menu_swa b/services/registration/my_address_menu_swa new file mode 100644 index 0000000..8ec744e --- /dev/null +++ b/services/registration/my_address_menu_swa @@ -0,0 +1 @@ +Anwani yangu diff --git a/services/registration/my_balance b/services/registration/my_balance new file mode 100644 index 0000000..15c5901 --- /dev/null +++ b/services/registration/my_balance @@ -0,0 +1 @@ +Your balance is: 0.00 SRF \ No newline at end of file diff --git a/services/registration/my_balance.vis b/services/registration/my_balance.vis new file mode 100644 index 0000000..151c6d8 --- /dev/null +++ b/services/registration/my_balance.vis @@ -0,0 +1,5 @@ +LOAD reset_incorrect 0 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH pin_entry flag_account_authorized 0 +LOAD quit_with_balance 0 +HALT diff --git a/services/registration/my_balance_menu b/services/registration/my_balance_menu new file mode 100644 index 0000000..fdd930b --- /dev/null +++ b/services/registration/my_balance_menu @@ -0,0 +1 @@ +My balance \ No newline at end of file diff --git a/services/registration/my_balance_menu_swa b/services/registration/my_balance_menu_swa new file mode 100644 index 0000000..810c386 --- /dev/null +++ b/services/registration/my_balance_menu_swa @@ -0,0 +1 @@ +Salio langu \ No newline at end of file diff --git a/services/registration/my_balance_swa b/services/registration/my_balance_swa new file mode 100644 index 0000000..3f6741f --- /dev/null +++ b/services/registration/my_balance_swa @@ -0,0 +1 @@ +Salio lako ni: 0.00 SRF diff --git a/services/registration/no_menu b/services/registration/no_menu new file mode 100644 index 0000000..7ecb56e --- /dev/null +++ b/services/registration/no_menu @@ -0,0 +1 @@ +no diff --git a/services/registration/no_menu_swa b/services/registration/no_menu_swa new file mode 100644 index 0000000..2ece186 --- /dev/null +++ b/services/registration/no_menu_swa @@ -0,0 +1 @@ +la diff --git a/services/registration/pin_entry b/services/registration/pin_entry new file mode 100644 index 0000000..cbb44ca --- /dev/null +++ b/services/registration/pin_entry @@ -0,0 +1 @@ +Please enter your PIN: \ No newline at end of file diff --git a/services/registration/pin_entry.vis b/services/registration/pin_entry.vis new file mode 100644 index 0000000..2eaf40f --- /dev/null +++ b/services/registration/pin_entry.vis @@ -0,0 +1,4 @@ +LOAD authorize_account 0 +HALT +RELOAD authorize_account +MOVE _ diff --git a/services/registration/pin_entry_swa b/services/registration/pin_entry_swa new file mode 100644 index 0000000..bffbf0f --- /dev/null +++ b/services/registration/pin_entry_swa @@ -0,0 +1 @@ +Tafadhali weka PIN yako diff --git a/services/registration/pin_management b/services/registration/pin_management new file mode 100644 index 0000000..876d1f7 --- /dev/null +++ b/services/registration/pin_management @@ -0,0 +1 @@ +PIN Management diff --git a/services/registration/pin_management.vis b/services/registration/pin_management.vis new file mode 100644 index 0000000..ecd5a8c --- /dev/null +++ b/services/registration/pin_management.vis @@ -0,0 +1,6 @@ +MOUT change_pin 1 +MOUT reset_pin 2 +MOUT guard_pin 3 +MOUT back 0 +HALT +INCMP _ 0 diff --git a/services/registration/pin_management_swa b/services/registration/pin_management_swa new file mode 100644 index 0000000..e69de29 diff --git a/services/registration/pin_options_menu b/services/registration/pin_options_menu new file mode 100644 index 0000000..778d28d --- /dev/null +++ b/services/registration/pin_options_menu @@ -0,0 +1 @@ +PIN options \ No newline at end of file diff --git a/services/registration/pin_options_menu_swa b/services/registration/pin_options_menu_swa new file mode 100644 index 0000000..e47ca0f --- /dev/null +++ b/services/registration/pin_options_menu_swa @@ -0,0 +1 @@ +Mipangilio ya PIN \ No newline at end of file diff --git a/services/registration/pp.csv b/services/registration/pp.csv new file mode 100644 index 0000000..fd552e4 --- /dev/null +++ b/services/registration/pp.csv @@ -0,0 +1,16 @@ +flag,flag_language_set,8,checks whether the user has set their prefered language +flag,flag_account_created,9,this is set when an account has been created on the API +flag,flag_account_creation_failed,10,this is set when there's an error from the API during account creation +flag,flag_account_pending,11,this is set when an account does not have a status of SUCCESS +flag,flag_account_success,12,this is set when an account has a status of SUCCESS +flag,flag_pin_mismatch,13,this is set when the confirmation PIN matches the initial PIN during registration +flag,flag_pin_set,14,this is set when a newly registered user sets a PIN. This must be present for an account to access the main menu +flag,flag_account_authorized,15,this is set to allow a user access guarded nodes after providing a correct PIN +flag,flag_invalid_recipient,16,this is set when the transaction recipient is invalid +flag,flag_invalid_recipient_with_invite,17,this is set when the transaction recipient is valid but not on the platform +flag,flag_invalid_amount,18,this is set when the given transaction amount is invalid +flag,flag_incorrect_pin,19,this is set when the provided PIN is invalid or does not match the current account's PIN +flag,flag_valid_pin,20,this is set when the given PIN is valid +flag,flag_allow_update,21,this is set to allow a user to update their profile data +flag,flag_single_edit,22,this is set to allow a user to edit a single profile item such as year of birth +flag,flag_incorrect_date_format,23,this is set when the given year of birth is invalid diff --git a/services/registration/profile_menu b/services/registration/profile_menu new file mode 100644 index 0000000..99455ed --- /dev/null +++ b/services/registration/profile_menu @@ -0,0 +1 @@ +Profile diff --git a/services/registration/profile_menu_swa b/services/registration/profile_menu_swa new file mode 100644 index 0000000..b34a86b --- /dev/null +++ b/services/registration/profile_menu_swa @@ -0,0 +1 @@ +Wasifu wangu diff --git a/services/registration/quit.vis b/services/registration/quit.vis new file mode 100644 index 0000000..0c8bb46 --- /dev/null +++ b/services/registration/quit.vis @@ -0,0 +1,2 @@ +LOAD quit 0 +HALT diff --git a/services/registration/reset_pin_menu b/services/registration/reset_pin_menu new file mode 100644 index 0000000..1f5d676 --- /dev/null +++ b/services/registration/reset_pin_menu @@ -0,0 +1 @@ +Reset other's PIN \ No newline at end of file diff --git a/services/registration/reset_pin_menu_swa b/services/registration/reset_pin_menu_swa new file mode 100644 index 0000000..d17e316 --- /dev/null +++ b/services/registration/reset_pin_menu_swa @@ -0,0 +1 @@ +Badili PIN ya mwenzio diff --git a/services/registration/root b/services/registration/root new file mode 100644 index 0000000..3928a82 --- /dev/null +++ b/services/registration/root @@ -0,0 +1 @@ +Welcome to Sarafu Network \ No newline at end of file diff --git a/services/registration/root.vis b/services/registration/root.vis new file mode 100644 index 0000000..6e3b79d --- /dev/null +++ b/services/registration/root.vis @@ -0,0 +1,7 @@ +CATCH select_language flag_language_set 0 +CATCH terms flag_account_created 0 +LOAD check_account_status 0 +CATCH account_pending flag_account_pending 1 +CATCH create_pin flag_pin_set 0 +CATCH main flag_account_success 1 +HALT diff --git a/services/registration/root_swa b/services/registration/root_swa new file mode 100644 index 0000000..75bb624 --- /dev/null +++ b/services/registration/root_swa @@ -0,0 +1 @@ +Karibu Sarafu Network \ No newline at end of file diff --git a/services/registration/select_gender b/services/registration/select_gender new file mode 100644 index 0000000..f8a6f47 --- /dev/null +++ b/services/registration/select_gender @@ -0,0 +1 @@ +Select gender: \ No newline at end of file diff --git a/services/registration/select_gender.vis b/services/registration/select_gender.vis new file mode 100644 index 0000000..dd354fc --- /dev/null +++ b/services/registration/select_gender.vis @@ -0,0 +1,13 @@ +LOAD save_familyname 0 +CATCH update_success flag_allow_update 1 +MOUT male 1 +MOUT female 2 +MOUT unspecified 3 +MOUT back 0 +HALT +LOAD save_gender 0 +CATCH pin_entry flag_single_edit 1 +INCMP _ 0 +INCMP enter_yob 1 +INCMP enter_yob 2 +INCMP enter_yob 3 diff --git a/services/registration/select_gender_swa b/services/registration/select_gender_swa new file mode 100644 index 0000000..2b3a748 --- /dev/null +++ b/services/registration/select_gender_swa @@ -0,0 +1 @@ +Chagua jinsia \ No newline at end of file diff --git a/services/registration/select_language b/services/registration/select_language new file mode 100644 index 0000000..b3d4304 --- /dev/null +++ b/services/registration/select_language @@ -0,0 +1,2 @@ +Welcome to Sarafu Network +Please select a language \ No newline at end of file diff --git a/services/registration/select_language.vis b/services/registration/select_language.vis new file mode 100644 index 0000000..1dd92ae --- /dev/null +++ b/services/registration/select_language.vis @@ -0,0 +1,6 @@ +MOUT english 0 +MOUT kiswahili 1 +HALT +INCMP terms 0 +INCMP terms 1 +INCMP . * diff --git a/services/registration/send b/services/registration/send new file mode 100644 index 0000000..2d5ad69 --- /dev/null +++ b/services/registration/send @@ -0,0 +1 @@ +Enter recipient's phone number: diff --git a/services/registration/send.vis b/services/registration/send.vis new file mode 100644 index 0000000..e120302 --- /dev/null +++ b/services/registration/send.vis @@ -0,0 +1,8 @@ +LOAD transaction_reset 0 +MOUT back 0 +HALT +LOAD validate_recipient 20 +RELOAD validate_recipient +CATCH invalid_recipient flag_invalid_recipient 1 +INCMP _ 0 +INCMP amount * diff --git a/services/registration/send_menu b/services/registration/send_menu new file mode 100644 index 0000000..5f5a837 --- /dev/null +++ b/services/registration/send_menu @@ -0,0 +1 @@ +Send \ No newline at end of file diff --git a/services/registration/send_menu_swa b/services/registration/send_menu_swa new file mode 100644 index 0000000..605c8e8 --- /dev/null +++ b/services/registration/send_menu_swa @@ -0,0 +1 @@ +Tuma \ No newline at end of file diff --git a/services/registration/send_swa b/services/registration/send_swa new file mode 100644 index 0000000..016760e --- /dev/null +++ b/services/registration/send_swa @@ -0,0 +1 @@ +Weka nambari ya simu: \ No newline at end of file diff --git a/services/registration/terms b/services/registration/terms new file mode 100644 index 0000000..05b8c11 --- /dev/null +++ b/services/registration/terms @@ -0,0 +1 @@ +Do you agree to terms and conditions? \ No newline at end of file diff --git a/services/registration/terms.vis b/services/registration/terms.vis new file mode 100644 index 0000000..dea6797 --- /dev/null +++ b/services/registration/terms.vis @@ -0,0 +1,7 @@ +LOAD select_language 0 +RELOAD select_language +MOUT yes 0 +MOUT no 1 +HALT +INCMP create_pin 0 +INCMP quit * diff --git a/services/registration/terms_swa b/services/registration/terms_swa new file mode 100644 index 0000000..7113cd7 --- /dev/null +++ b/services/registration/terms_swa @@ -0,0 +1 @@ +Kwa kutumia hii huduma umekubali sheria na masharti? \ No newline at end of file diff --git a/services/registration/transaction_initiated.vis b/services/registration/transaction_initiated.vis new file mode 100644 index 0000000..3b8b9f6 --- /dev/null +++ b/services/registration/transaction_initiated.vis @@ -0,0 +1,11 @@ +LOAD reset_incorrect 6 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH _ flag_account_authorized 0 +LOAD get_amount 10 +MAP get_amount +RELOAD get_recipient +MAP get_recipient +RELOAD get_sender +MAP get_sender +LOAD initiate_transaction 0 +HALT diff --git a/services/registration/transaction_pin b/services/registration/transaction_pin new file mode 100644 index 0000000..39a1206 --- /dev/null +++ b/services/registration/transaction_pin @@ -0,0 +1,2 @@ +{{.get_recipient}} will receive {{.validate_amount}} from {{.get_sender}} +Please enter your PIN to confirm: diff --git a/services/registration/transaction_pin.vis b/services/registration/transaction_pin.vis new file mode 100644 index 0000000..cadbdcf --- /dev/null +++ b/services/registration/transaction_pin.vis @@ -0,0 +1,14 @@ +MAP validate_amount +RELOAD get_recipient +MAP get_recipient +RELOAD get_sender +MAP get_sender +MOUT back 0 +MOUT quit 9 +HALT +LOAD authorize_account 6 +RELOAD authorize_account +CATCH incorrect_pin flag_incorrect_pin 1 +INCMP _ 0 +INCMP quit 9 +INCMP transaction_initiated * diff --git a/services/registration/transaction_pin_swa b/services/registration/transaction_pin_swa new file mode 100644 index 0000000..8529f0e --- /dev/null +++ b/services/registration/transaction_pin_swa @@ -0,0 +1,2 @@ +{{.get_recipient}} atapokea {{.validate_amount}} kutoka kwa {{.get_sender}} +Tafadhali weka PIN yako kudhibitisha: \ No newline at end of file diff --git a/services/registration/unspecified_menu b/services/registration/unspecified_menu new file mode 100644 index 0000000..0e1d0c3 --- /dev/null +++ b/services/registration/unspecified_menu @@ -0,0 +1 @@ +Unspecified diff --git a/services/registration/unspecified_menu_swa b/services/registration/unspecified_menu_swa new file mode 100644 index 0000000..009301f --- /dev/null +++ b/services/registration/unspecified_menu_swa @@ -0,0 +1 @@ +Haijabainishwa \ No newline at end of file diff --git a/services/registration/update_success b/services/registration/update_success new file mode 100644 index 0000000..652942a --- /dev/null +++ b/services/registration/update_success @@ -0,0 +1 @@ +Profile updated successfully diff --git a/services/registration/update_success.vis b/services/registration/update_success.vis new file mode 100644 index 0000000..832ef22 --- /dev/null +++ b/services/registration/update_success.vis @@ -0,0 +1,5 @@ +MOUT back 0 +MOUT quit 9 +HALT +INCMP ^ 0 +INCMP quit 9 diff --git a/services/registration/update_success_swa b/services/registration/update_success_swa new file mode 100644 index 0000000..19949ad --- /dev/null +++ b/services/registration/update_success_swa @@ -0,0 +1 @@ +Akaunti imeupdatiwa diff --git a/services/registration/view_menu b/services/registration/view_menu new file mode 100644 index 0000000..03add31 --- /dev/null +++ b/services/registration/view_menu @@ -0,0 +1 @@ +View profile \ No newline at end of file diff --git a/services/registration/view_profile b/services/registration/view_profile new file mode 100644 index 0000000..7708bd5 --- /dev/null +++ b/services/registration/view_profile @@ -0,0 +1,2 @@ +My profile: +{{.get_profile_info}} diff --git a/services/registration/view_profile.vis b/services/registration/view_profile.vis new file mode 100644 index 0000000..a7ffee4 --- /dev/null +++ b/services/registration/view_profile.vis @@ -0,0 +1,8 @@ +LOAD get_profile_info 0 +MAP get_profile_info +LOAD reset_incorrect 6 +CATCH incorrect_pin flag_incorrect_pin 1 +CATCH pin_entry flag_account_authorized 0 +MOUT back 0 +HALT +INCMP _ 0 diff --git a/services/registration/view_profile_swa b/services/registration/view_profile_swa new file mode 100644 index 0000000..8a12b7d --- /dev/null +++ b/services/registration/view_profile_swa @@ -0,0 +1 @@ +Wasifu wangu \ No newline at end of file diff --git a/services/registration/view_swa b/services/registration/view_swa new file mode 100644 index 0000000..7483b4b --- /dev/null +++ b/services/registration/view_swa @@ -0,0 +1 @@ +Angalia Wasifu diff --git a/services/registration/vouchers_menu b/services/registration/vouchers_menu new file mode 100644 index 0000000..5084c32 --- /dev/null +++ b/services/registration/vouchers_menu @@ -0,0 +1 @@ +My Vouchers \ No newline at end of file diff --git a/services/registration/vouchers_menu_swa b/services/registration/vouchers_menu_swa new file mode 100644 index 0000000..64ba54e --- /dev/null +++ b/services/registration/vouchers_menu_swa @@ -0,0 +1 @@ +Sarafu yangu \ No newline at end of file diff --git a/services/registration/yes_menu b/services/registration/yes_menu new file mode 100644 index 0000000..7cfab5b --- /dev/null +++ b/services/registration/yes_menu @@ -0,0 +1 @@ +yes diff --git a/services/registration/yes_menu_swa b/services/registration/yes_menu_swa new file mode 100644 index 0000000..6908a6c --- /dev/null +++ b/services/registration/yes_menu_swa @@ -0,0 +1 @@ +ndio