wip-account-creation #4

Merged
lash merged 143 commits from wip-account-creation into master 2024-08-30 14:37:58 +02:00
6 changed files with 844 additions and 815 deletions
Showing only changes of commit d5f4f9ea5f - Show all commits

View File

@ -1,790 +1,21 @@
package main package main
import ( import (
"bytes"
"context" "context"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"os" "os"
"path" "path"
"regexp"
"time"
"git.defalsify.org/vise.git/cache" "git.defalsify.org/vise.git/cache"
"git.defalsify.org/vise.git/engine" "git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/lang"
"git.defalsify.org/vise.git/persist" "git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource" "git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state" "git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/server/handlers" "git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/utils" "git.grassecon.net/urdt/ussd/internal/handlers/ussd"
) )
const (
USERFLAG_LANGUAGE_SET = iota + state.FLAG_USERSTART
USERFLAG_ACCOUNT_CREATED
USERFLAG_ACCOUNT_PENDING
USERFLAG_ACCOUNT_SUCCESS
USERFLAG_ACCOUNT_UNLOCKED
USERFLAG_INVALID_RECIPIENT
USERFLAG_INVALID_RECIPIENT_WITH_INVITE
USERFLAG_INCORRECTPIN
USERFLAG_UNLOCKFORUPDATE
USERFLAG_INVALID_AMOUNT
USERFLAG_QUERYPIN
USERFLAG_VALIDPIN
USERFLAG_INVALIDPIN
USERFLAG_INCORRECTDATEFORMAT
)
type fsData struct {
path string
st *state.State
}
func codeFromCtx(ctx context.Context) string {
var code string
engine.Logg.DebugCtxf(ctx, "in msg", "ctx", ctx, "val", code)
if ctx.Value("Language") != nil {
lang := ctx.Value("Language").(lang.Language)
code = lang.Code
}
return code
}
func (fsd *fsData) save_firstname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
name := string(input)
accountData["FirstName"] = name
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_familyname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
//Save name
secondname := string(input)
accountData["FamilyName"] = secondname
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_yob(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
yob := string(input)
accountData["YOB"] = yob
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_location(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
location := string(input)
accountData["Location"] = location
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_gender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
gender := string(input)
switch gender {
case "1":
gender = "Male"
case "2":
gender = "Female"
case "3":
gender = "Other"
}
accountData["Gender"] = gender
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_offerings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
offerings := string(input)
accountData["Offerings"] = offerings
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) set_language(ctx context.Context, sym string, input []byte) (resource.Result, error) {
inputStr := string(input)
res := resource.Result{}
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:
}
res.FlagSet = append(res.FlagSet, USERFLAG_LANGUAGE_SET)
return res, nil
}
func (fsd *fsData) create_account(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
f, err := os.OpenFile(fp, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return res, err
}
f.Close()
accountResp, err := handlers.CreateAccount()
if err != nil {
fmt.Println("Failed to create account:", err)
return res, err
}
accountData := map[string]string{
"TrackingId": accountResp.Result.TrackingId,
"PublicKey": accountResp.Result.PublicKey,
"CustodialId": accountResp.Result.CustodialId.String(),
"Status": "PENDING",
}
jsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, jsonData, 0644)
if err != nil {
return res, err
}
res.FlagSet = append(res.FlagSet, USERFLAG_ACCOUNT_CREATED)
return res, err
}
func (fsd *fsData) reset_unlock_for_update(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, USERFLAG_UNLOCKFORUPDATE)
return res, nil
}
func (fsd *fsData) reset_account_unlocked(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (fsd *fsData) check_identifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (fsd *fsData) unlock(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
pin := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 1 {
if pin != accountData["AccountPIN"] {
res.FlagSet = append(res.FlagSet, USERFLAG_INCORRECTPIN)
res.FlagReset = append(res.FlagReset, USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
if fsd.st.MatchFlag(USERFLAG_ACCOUNT_UNLOCKED, false) {
res.FlagReset = append(res.FlagReset, USERFLAG_INCORRECTPIN)
res.FlagSet = append(res.FlagSet, USERFLAG_UNLOCKFORUPDATE)
res.FlagSet = append(res.FlagSet, USERFLAG_ACCOUNT_UNLOCKED)
} else {
res.FlagReset = append(res.FlagReset, USERFLAG_ACCOUNT_UNLOCKED)
}
}
return res, nil
}
func (fsd *fsData) reset_incorrect_pin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, USERFLAG_INCORRECTPIN)
return res, nil
}
func (fsd *fsData) check_account_status(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
//status, err := checkAccountStatus(accountData["TrackingId"])
status, err := handlers.CheckAccountStatus(accountData["TrackingId"])
if err != nil {
fmt.Println("Error checking account status:", err)
return res, nil
}
accountData["Status"] = status
if status == "SUCCESS" {
res.FlagSet = append(res.FlagSet, USERFLAG_ACCOUNT_SUCCESS)
res.FlagReset = append(res.FlagReset, USERFLAG_ACCOUNT_PENDING)
} else {
res.FlagReset = append(res.FlagSet, USERFLAG_ACCOUNT_SUCCESS)
res.FlagSet = append(res.FlagReset, USERFLAG_ACCOUNT_PENDING)
}
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (fsd *fsData) quit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
switch codeFromCtx(ctx) {
case "swa":
res.Content = "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
default:
res.Content = "Thank you for using Sarafu. Goodbye!"
}
res.FlagReset = append(res.FlagReset, USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (fsd *fsData) verify_yob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
date := string(input)
dateRegex := regexp.MustCompile(`^\d{2}/\d{2}/\d{4}$`)
isCorrectFormat := dateRegex.MatchString(date)
if !isCorrectFormat {
res.FlagSet = append(res.FlagSet, USERFLAG_INCORRECTDATEFORMAT)
} else {
res.FlagReset = append(res.FlagReset, USERFLAG_INCORRECTDATEFORMAT)
}
return res, nil
}
func (fsd *fsData) reset_incorrect_yob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, USERFLAG_INCORRECTDATEFORMAT)
return res, nil
}
func (fsd *fsData) check_balance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := handlers.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = balance
return res, nil
}
func (fsd *fsData) validate_recipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
recipient := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if recipient != "0" {
// mimic invalid number check
if recipient == "000" {
res.FlagSet = append(res.FlagSet, USERFLAG_INVALID_RECIPIENT)
res.Content = recipient
return res, nil
}
accountData["Recipient"] = recipient
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) transaction_reset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the recipient
accountData["Recipient"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, USERFLAG_INVALID_RECIPIENT, USERFLAG_INVALID_RECIPIENT_WITH_INVITE)
return res, nil
}
func (fsd *fsData) reset_transaction_amount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the amount
accountData["Amount"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, USERFLAG_INVALID_AMOUNT)
return res, nil
}
func (fsd *fsData) max_amount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
// mimic a max amount
res.Content = "10.00"
return res, nil
}
func (fsd *fsData) validate_amount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
amount := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if amount != "0" {
// mimic invalid amount
if amount == "00" {
res.FlagSet = append(res.FlagSet, USERFLAG_INVALID_AMOUNT)
res.Content = amount
return res, nil
}
res.Content = amount
accountData["Amount"] = amount
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
return res, nil
}
func (fsd *fsData) get_recipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["Recipient"]
return res, nil
}
func (fsd *fsData) get_profile_info(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
name := accountData["FirstName"] + " " + accountData["FamilyName"]
gender := accountData["Gender"]
yob := accountData["YOB"]
location := accountData["Location"]
offerings := accountData["Offerings"]
layout := "02/01/2006"
birthdate, err := time.Parse(layout, yob)
if err != nil {
return res, err
}
currentDate := time.Now()
formattedDate := currentDate.Format(layout)
today, err := time.Parse(layout, formattedDate)
if err != nil {
return res, nil
}
age := utils.CalculateAge(birthdate, today)
formattedData := fmt.Sprintf("Name: %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n", name, gender, age, location, offerings)
res.Content = formattedData
return res, nil
}
func (fsd *fsData) get_sender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (fsd *fsData) quit_with_balance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := handlers.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = fmt.Sprintf("Your account balance is: %s", balance)
res.FlagReset = append(res.FlagReset, USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (fsd *fsData) save_pin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
accountPIN := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
accountData["AccountPIN"] = accountPIN
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (fsd *fsData) verify_pin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if bytes.Equal(input, []byte(accountData["AccountPIN"])) {
res.FlagSet = []uint32{USERFLAG_VALIDPIN}
res.FlagReset = []uint32{USERFLAG_INVALIDPIN}
} else {
res.FlagSet = []uint32{USERFLAG_INVALIDPIN}
}
return res, nil
}
var ( var (
scriptDir = path.Join("services", "registration") scriptDir = path.Join("services", "registration")
) )
@ -802,13 +33,13 @@ func main() {
fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir) fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir)
ctx := context.Background() ctx := context.Background()
st := state.NewState(15) st := state.NewState(14)
st.UseDebug() st.UseDebug()
state.FlagDebugger.Register(USERFLAG_LANGUAGE_SET, "LANGUAGE_CHANGE") state.FlagDebugger.Register(models.USERFLAG_LANGUAGE_SET, "LANGUAGE_CHANGE")
state.FlagDebugger.Register(USERFLAG_ACCOUNT_CREATED, "ACCOUNT_CREATED") state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_CREATED, "ACCOUNT_CREATED")
state.FlagDebugger.Register(USERFLAG_ACCOUNT_SUCCESS, "ACCOUNT_SUCCESS") state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_SUCCESS, "ACCOUNT_SUCCESS")
state.FlagDebugger.Register(USERFLAG_ACCOUNT_PENDING, "ACCOUNT_PENDING") state.FlagDebugger.Register(models.USERFLAG_ACCOUNT_PENDING, "ACCOUNT_PENDING")
state.FlagDebugger.Register(USERFLAG_INCORRECTPIN, "INCORRECTPIN") state.FlagDebugger.Register(models.USERFLAG_INCORRECTPIN, "INCORRECTPIN")
rfs := resource.NewFsResource(scriptDir) rfs := resource.NewFsResource(scriptDir)
ca := cache.NewCache() ca := cache.NewCache()
@ -840,39 +71,38 @@ func main() {
} }
fp := path.Join(dp, sessionId) fp := path.Join(dp, sessionId)
fs := &fsData{
path: fp, ussdHandlers := ussd.NewHandlers(fp, &st)
st: &st,
} rfs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
rfs.AddLocalFunc("select_language", fs.set_language) rfs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rfs.AddLocalFunc("create_account", fs.create_account) rfs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rfs.AddLocalFunc("save_pin", fs.save_pin) rfs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rfs.AddLocalFunc("verify_pin", fs.verify_pin) rfs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rfs.AddLocalFunc("check_identifier", fs.check_identifier) rfs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rfs.AddLocalFunc("check_account_status", fs.check_account_status) rfs.AddLocalFunc("unlock_account", ussdHandlers.Unlock)
rfs.AddLocalFunc("unlock_account", fs.unlock) rfs.AddLocalFunc("quit", ussdHandlers.Quit)
rfs.AddLocalFunc("quit", fs.quit) rfs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rfs.AddLocalFunc("check_balance", fs.check_balance) rfs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rfs.AddLocalFunc("validate_recipient", fs.validate_recipient) rfs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rfs.AddLocalFunc("transaction_reset", fs.transaction_reset) rfs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rfs.AddLocalFunc("max_amount", fs.max_amount) rfs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rfs.AddLocalFunc("validate_amount", fs.validate_amount) rfs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rfs.AddLocalFunc("reset_transaction_amount", fs.reset_transaction_amount) rfs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rfs.AddLocalFunc("get_recipient", fs.get_recipient) rfs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rfs.AddLocalFunc("get_sender", fs.get_sender) rfs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rfs.AddLocalFunc("reset_incorrect", fs.reset_incorrect_pin) rfs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rfs.AddLocalFunc("save_firstname", fs.save_firstname) rfs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rfs.AddLocalFunc("save_familyname", fs.save_familyname) rfs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rfs.AddLocalFunc("save_gender", fs.save_gender) rfs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rfs.AddLocalFunc("save_location", fs.save_location) rfs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rfs.AddLocalFunc("save_yob", fs.save_yob) rfs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rfs.AddLocalFunc("save_offerings", fs.save_offerings) rfs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rfs.AddLocalFunc("quit_with_balance", fs.quit_with_balance) rfs.AddLocalFunc("reset_unlocked", ussdHandlers.ResetAccountUnlocked)
rfs.AddLocalFunc("reset_unlocked", fs.reset_account_unlocked) rfs.AddLocalFunc("reset_unlock_for_update", ussdHandlers.ResetUnlockForUpdate)
rfs.AddLocalFunc("reset_unlock_for_update", fs.reset_unlock_for_update) rfs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rfs.AddLocalFunc("get_profile_info", fs.get_profile_info) rfs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rfs.AddLocalFunc("verify_yob", fs.verify_yob) rfs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
rfs.AddLocalFunc("reset_incorrect_date_format", fs.reset_incorrect_yob)
cont, err := en.Init(ctx) cont, err := en.Init(ctx)
en.SetDebugger(engine.NewSimpleDebug(nil)) en.SetDebugger(engine.NewSimpleDebug(nil))

View File

@ -1,4 +1,4 @@
package handlers package server
import ( import (
"encoding/json" "encoding/json"
@ -9,7 +9,6 @@ import (
"git.grassecon.net/urdt/ussd/internal/models" "git.grassecon.net/urdt/ussd/internal/models"
) )
func CheckAccountStatus(trackingId string) (string, error) { func CheckAccountStatus(trackingId string) (string, error) {
resp, err := http.Get(config.TrackStatusURL + trackingId) resp, err := http.Get(config.TrackStatusURL + trackingId)
if err != nil { if err != nil {

View File

@ -1,4 +1,4 @@
package handlers package server
import ( import (
"encoding/json" "encoding/json"

View File

@ -1,4 +1,4 @@
package handlers package server
import ( import (
"encoding/json" "encoding/json"

View File

@ -0,0 +1,780 @@
package ussd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"regexp"
"time"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/lang"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/utils"
)
type FSData struct {
Path string
St *state.State
}
type Handlers struct {
fs *FSData
}
func NewHandlers(path string, st *state.State) *Handlers {
return &Handlers{
fs: &FSData{
Path: path,
St: st,
},
}
}
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
inputStr := string(input)
res := resource.Result{}
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:
}
res.FlagSet = append(res.FlagSet, models.USERFLAG_LANGUAGE_SET)
return res, nil
}
func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
f, err := os.OpenFile(fp, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return res, err
}
f.Close()
accountResp, err := server.CreateAccount()
if err != nil {
fmt.Println("Failed to create account:", err)
return res, err
}
accountData := map[string]string{
"TrackingId": accountResp.Result.TrackingId,
"PublicKey": accountResp.Result.PublicKey,
"CustodialId": accountResp.Result.CustodialId.String(),
"Status": "PENDING",
}
jsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, jsonData, 0644)
if err != nil {
return res, err
}
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_CREATED)
return res, err
}
func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
accountPIN := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
accountData["AccountPIN"] = accountPIN
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if bytes.Equal(input, []byte(accountData["AccountPIN"])) {
res.FlagSet = []uint32{models.USERFLAG_VALIDPIN}
res.FlagReset = []uint32{models.USERFLAG_PINMISMATCH}
} else {
res.FlagSet = []uint32{models.USERFLAG_PINMISMATCH}
}
return res, nil
}
func codeFromCtx(ctx context.Context) string {
var code string
engine.Logg.DebugCtxf(ctx, "in msg", "ctx", ctx, "val", code)
if ctx.Value("Language") != nil {
lang := ctx.Value("Language").(lang.Language)
code = lang.Code
}
return code
}
func (h *Handlers) SaveFirstname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
name := string(input)
accountData["FirstName"] = name
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveFamilyname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
//Save name
secondname := string(input)
accountData["FamilyName"] = secondname
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveYob(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
yob := string(input)
accountData["YOB"] = yob
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveLocation(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
location := string(input)
accountData["Location"] = location
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
gender := string(input)
switch gender {
case "1":
gender = "Male"
case "2":
gender = "Female"
case "3":
gender = "Other"
}
accountData["Gender"] = gender
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
offerings := string(input)
accountData["Offerings"] = offerings
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) ResetUnlockForUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_UNLOCKFORUPDATE)
return res, nil
}
func (h *Handlers) ResetAccountUnlocked(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (h *Handlers) Unlock(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
pin := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 1 {
if pin != accountData["AccountPIN"] {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INCORRECTPIN)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
if h.fs.St.MatchFlag(models.USERFLAG_ACCOUNT_UNLOCKED, false) {
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTPIN)
res.FlagSet = append(res.FlagSet, models.USERFLAG_UNLOCKFORUPDATE)
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_UNLOCKED)
} else {
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
}
}
return res, nil
}
func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTPIN)
return res, nil
}
func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
//status, err := checkAccountStatus(accountData["TrackingId"])
status, err := server.CheckAccountStatus(accountData["TrackingId"])
if err != nil {
fmt.Println("Error checking account status:", err)
return res, nil
}
accountData["Status"] = status
if status == "SUCCESS" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_SUCCESS)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_PENDING)
} else {
res.FlagReset = append(res.FlagSet, models.USERFLAG_ACCOUNT_SUCCESS)
res.FlagSet = append(res.FlagReset, models.USERFLAG_ACCOUNT_PENDING)
}
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
switch codeFromCtx(ctx) {
case "swa":
res.Content = "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
default:
res.Content = "Thank you for using Sarafu. Goodbye!"
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
date := string(input)
dateRegex := regexp.MustCompile(`^\d{2}/\d{2}/\d{4}$`)
isCorrectFormat := dateRegex.MatchString(date)
if !isCorrectFormat {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INCORRECTDATEFORMAT)
} else {
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTDATEFORMAT)
}
return res, nil
}
func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTDATEFORMAT)
return res, nil
}
func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := server.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = balance
return res, nil
}
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
recipient := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if recipient != "0" {
// mimic invalid number check
if recipient == "000" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INVALID_RECIPIENT)
res.Content = recipient
return res, nil
}
accountData["Recipient"] = recipient
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the recipient
accountData["Recipient"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INVALID_RECIPIENT, models.USERFLAG_INVALID_RECIPIENT_WITH_INVITE)
return res, nil
}
func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the amount
accountData["Amount"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INVALID_AMOUNT)
return res, nil
}
func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
// mimic a max amount
res.Content = "10.00"
return res, nil
}
func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
amount := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if amount != "0" {
// mimic invalid amount
if amount == "00" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INVALID_AMOUNT)
res.Content = amount
return res, nil
}
res.Content = amount
accountData["Amount"] = amount
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
return res, nil
}
func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["Recipient"]
return res, nil
}
func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
name := accountData["FirstName"] + " " + accountData["FamilyName"]
gender := accountData["Gender"]
yob := accountData["YOB"]
location := accountData["Location"]
offerings := accountData["Offerings"]
layout := "02/01/2006"
birthdate, err := time.Parse(layout, yob)
if err != nil {
return res, err
}
currentDate := time.Now()
formattedDate := currentDate.Format(layout)
today, err := time.Parse(layout, formattedDate)
if err != nil {
return res, nil
}
age := utils.CalculateAge(birthdate, today)
formattedData := fmt.Sprintf("Name: %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n", name, gender, age, location, offerings)
res.Content = formattedData
return res, nil
}
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (h *Handlers) QuitWithBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := server.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = fmt.Sprintf("Your account balance is: %s", balance)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}

20
internal/models/flags.go Normal file
View File

@ -0,0 +1,20 @@
package models
import "git.defalsify.org/vise.git/state"
const (
USERFLAG_LANGUAGE_SET = iota + state.FLAG_USERSTART
USERFLAG_ACCOUNT_CREATED
USERFLAG_ACCOUNT_PENDING
USERFLAG_ACCOUNT_SUCCESS
USERFLAG_ACCOUNT_UNLOCKED
USERFLAG_INVALID_RECIPIENT
USERFLAG_INVALID_RECIPIENT_WITH_INVITE
USERFLAG_INCORRECTPIN
USERFLAG_UNLOCKFORUPDATE
USERFLAG_INVALID_AMOUNT
USERFLAG_QUERYPIN
USERFLAG_VALIDPIN
USERFLAG_PINMISMATCH
USERFLAG_INCORRECTDATEFORMAT
)