forked from urdt/ussd
Merge branch 'master' into voucher-details
This commit is contained in:
@@ -118,6 +118,9 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
|
||||
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
|
||||
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
|
||||
ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo)
|
||||
ls.DbRs.AddLocalFunc("check_transactions", ussdHandlers.CheckTransactions)
|
||||
ls.DbRs.AddLocalFunc("get_transactions", ussdHandlers.GetTransactionsList)
|
||||
ls.DbRs.AddLocalFunc("view_statement", ussdHandlers.ViewTransactionStatement)
|
||||
|
||||
return ussdHandlers, nil
|
||||
}
|
||||
|
||||
@@ -35,12 +35,17 @@ var (
|
||||
errResponse *api.ErrResponse
|
||||
)
|
||||
|
||||
// Define the regex patterns as constants
|
||||
// Define the regex patterns as constants
|
||||
const (
|
||||
phoneRegex = `(\(\d{3}\)\s?|\d{3}[-.\s]?)?\d{3}[-.\s]?\d{4}`
|
||||
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
|
||||
}
|
||||
|
||||
// FlagManager handles centralized flag management
|
||||
type FlagManager struct {
|
||||
parser *asm.FlagParser
|
||||
@@ -95,17 +100,6 @@ func NewHandlers(appFlags *asm.FlagParser, userdataStore db.Db, adminstore *util
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// isValidPIN checks whether the given input is a 4 digit number
|
||||
func isValidPIN(pin string) bool {
|
||||
match, _ := regexp.MatchString(pinPattern, pin)
|
||||
return match
|
||||
}
|
||||
|
||||
func isValidPhoneNumber(phonenumber string) bool {
|
||||
match, _ := regexp.MatchString(phoneRegex, phonenumber)
|
||||
return match
|
||||
}
|
||||
|
||||
func (h *Handlers) WithPersister(pe *persist.Persister) *Handlers {
|
||||
if h.pe != nil {
|
||||
panic("persister already set")
|
||||
@@ -877,7 +871,7 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
|
||||
}
|
||||
blockedNumber := string(input)
|
||||
_, err = store.ReadEntry(ctx, blockedNumber, common.DATA_PUBLIC_KEY)
|
||||
if !isValidPhoneNumber(blockedNumber) {
|
||||
if !common.IsValidPhoneNumber(blockedNumber) {
|
||||
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
|
||||
return res, nil
|
||||
}
|
||||
@@ -898,10 +892,9 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ValidateRecipient validates that the given input is a valid phone number.
|
||||
// ValidateRecipient validates that the given input is valid.
|
||||
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
var err error
|
||||
store := h.userdataStore
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
@@ -909,13 +902,16 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
|
||||
recipient := string(input)
|
||||
|
||||
flag_invalid_recipient, _ := h.flagManager.GetFlag("flag_invalid_recipient")
|
||||
flag_invalid_recipient_with_invite, _ := h.flagManager.GetFlag("flag_invalid_recipient_with_invite")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
recipient := string(input)
|
||||
|
||||
if recipient != "0" {
|
||||
if !isValidPhoneNumber(recipient) {
|
||||
recipientType, err := common.CheckRecipient(recipient)
|
||||
if err != nil {
|
||||
// Invalid recipient format (not a phone number, address, or valid alias format)
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_recipient)
|
||||
res.Content = recipient
|
||||
|
||||
@@ -929,25 +925,61 @@ func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []by
|
||||
return res, err
|
||||
}
|
||||
|
||||
publicKey, err := store.ReadEntry(ctx, recipient, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
logg.InfoCtxf(ctx, "Unregistered number")
|
||||
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
|
||||
res.Content = recipient
|
||||
|
||||
return res, nil
|
||||
switch recipientType {
|
||||
case "phone number":
|
||||
// format the phone number
|
||||
formattedNumber, err := common.FormatPhoneNumber(recipient)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to format the phone number: %s", recipient, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
// Check if the phone number is registered
|
||||
publicKey, err := store.ReadEntry(ctx, formattedNumber, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
if db.IsNotFound(err) {
|
||||
logg.InfoCtxf(ctx, "Unregistered phone number: %s", recipient)
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_recipient_with_invite)
|
||||
res.Content = recipient
|
||||
return res, nil
|
||||
}
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err)
|
||||
return res, nil
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Save the publicKey as the recipient
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, publicKey)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", string(publicKey), "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
case "address":
|
||||
// Save the valid Ethereum address as the recipient
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(recipient))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", recipient, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
case "alias":
|
||||
// Call the API to validate and retrieve the address for the alias
|
||||
r, aliasErr := h.accountService.CheckAliasAddress(ctx, recipient)
|
||||
if aliasErr != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
res.Content = recipient
|
||||
|
||||
logg.ErrorCtxf(ctx, "failed on CheckAliasAddress", "error", aliasErr)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Alias validation succeeded, save the Ethereum address
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_RECIPIENT, []byte(r.Address))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write recipient entry with", "key", common.DATA_RECIPIENT, "value", r.Address, "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1632,3 +1664,172 @@ func (h *Handlers) GetVoucherDetails(ctx context.Context, sym string, input []by
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// CheckTransactions retrieves the transactions from the API using the "PublicKey" and stores to prefixDb
|
||||
func (h *Handlers) CheckTransactions(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")
|
||||
}
|
||||
|
||||
flag_no_transfers, _ := h.flagManager.GetFlag("flag_no_transfers")
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_error")
|
||||
|
||||
store := h.userdataStore
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Fetch transactions from the API using the public key
|
||||
transactionsResp, err := h.accountService.FetchTransactions(ctx, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
logg.ErrorCtxf(ctx, "failed on FetchTransactions", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Return if there are no transactions
|
||||
if len(transactionsResp) == 0 {
|
||||
res.FlagSet = append(res.FlagSet, flag_no_transfers)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
data := common.ProcessTransfers(transactionsResp)
|
||||
|
||||
// Store all transaction data
|
||||
dataMap := map[string]string{
|
||||
"txfrom": data.Senders,
|
||||
"txto": data.Recipients,
|
||||
"txval": data.TransferValues,
|
||||
"txaddr": data.Addresses,
|
||||
"txhash": data.TxHashes,
|
||||
"txdate": data.Dates,
|
||||
"txsym": data.Symbols,
|
||||
"txdeci": data.Decimals,
|
||||
}
|
||||
|
||||
for key, value := range dataMap {
|
||||
if err := h.prefixDb.Put(ctx, []byte(key), []byte(value)); err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write to prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_no_transfers)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetTransactionsList fetches the list of transactions and formats them
|
||||
func (h *Handlers) GetTransactionsList(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Read transactions from the store and format them
|
||||
TransactionSenders, err := h.prefixDb.Get(ctx, []byte("txfrom"))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionSenders from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionSyms, err := h.prefixDb.Get(ctx, []byte("txsym"))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionSyms from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionValues, err := h.prefixDb.Get(ctx, []byte("txval"))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionValues from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
TransactionDates, err := h.prefixDb.Get(ctx, []byte("txdate"))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "Failed to read the TransactionDates from prefixDb", "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Parse the data
|
||||
senders := strings.Split(string(TransactionSenders), "\n")
|
||||
syms := strings.Split(string(TransactionSyms), "\n")
|
||||
values := strings.Split(string(TransactionValues), "\n")
|
||||
dates := strings.Split(string(TransactionDates), "\n")
|
||||
|
||||
var formattedTransactions []string
|
||||
for i := 0; i < len(senders); i++ {
|
||||
sender := strings.TrimSpace(senders[i])
|
||||
sym := strings.TrimSpace(syms[i])
|
||||
value := strings.TrimSpace(values[i])
|
||||
date := strings.Split(strings.TrimSpace(dates[i]), " ")[0]
|
||||
|
||||
status := "received"
|
||||
if sender == string(publicKey) {
|
||||
status = "sent"
|
||||
}
|
||||
|
||||
formattedTransactions = append(formattedTransactions, fmt.Sprintf("%d:%s %s %s %s", i+1, status, value, sym, date))
|
||||
}
|
||||
|
||||
res.Content = strings.Join(formattedTransactions, "\n")
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// ViewTransactionStatement retrieves the transaction statement
|
||||
// and displays it to the user
|
||||
func (h *Handlers) ViewTransactionStatement(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
store := h.userdataStore
|
||||
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
flag_incorrect_statement, _ := h.flagManager.GetFlag("flag_incorrect_statement")
|
||||
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" || inputStr == "99" || inputStr == "11" || inputStr == "22" {
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Convert input string to integer
|
||||
index, err := strconv.Atoi(strings.TrimSpace(inputStr))
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("invalid input: must be a number between 1 and 10")
|
||||
}
|
||||
|
||||
if index < 1 || index > 10 {
|
||||
return res, fmt.Errorf("invalid input: index must be between 1 and 10")
|
||||
}
|
||||
|
||||
statement, err := common.GetTransferData(ctx, h.prefixDb, string(publicKey), index)
|
||||
if err != nil {
|
||||
return res, fmt.Errorf("failed to retrieve transfer data: %v", err)
|
||||
}
|
||||
|
||||
if statement == "" {
|
||||
res.FlagSet = append(res.FlagSet, flag_incorrect_statement)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_incorrect_statement)
|
||||
res.Content = statement
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user