Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
f5d2644031 | |||
09b4fa2860 | |||
a748c1b6b2 | |||
51bad64a51 | |||
212cd48249 | |||
7adc0c9c08 | |||
0a19a6c48b | |||
66b5843b0d | |||
df89fe69e1 | |||
9498242901 | |||
eab6dbd74c | |||
6159686a8e | |||
1d82bc2261 | |||
bfcdd79f33 | |||
d49b68597c | |||
1d9f4fc13e | |||
6fa0d8e2ff | |||
109a2a2020 | |||
bc6d8098f3 | |||
01e75e9217 | |||
f2b17880ba | |||
1d4f116079 | |||
44c52b6ed7 | |||
454f67b317 | |||
e1506a3dcf | |||
b22a4adec1 | |||
fc0043e3f6 | |||
d41ba79ae4 | |||
f36847d966 |
13
.dockerignore
Normal file
13
.dockerignore
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
!/cmd/africastalking
|
||||
!/common
|
||||
!/config
|
||||
!/initializers
|
||||
!/internal
|
||||
!/models
|
||||
!/remote
|
||||
!/services
|
||||
!/LICENSE
|
||||
!/README.md
|
||||
!/go.*
|
||||
!/.env.example
|
56
.github/workflows/docker.yaml
vendored
Normal file
56
.github/workflows/docker.yaml
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Login to GHCR Docker registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set outputs
|
||||
run: |
|
||||
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
|
||||
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: ./
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
build-args: |
|
||||
BUILD=${{ env.RELEASE_SHORT_COMMIT }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
tags: |
|
||||
ghcr.io/grassrootseconomics/urdt-ussd:latest
|
||||
ghcr.io/grassrootseconomics/urdt-ussd:${{ env.RELEASE_TAG }}
|
41
Dockerfile
Normal file
41
Dockerfile
Normal file
@ -0,0 +1,41 @@
|
||||
FROM golang:1.23.0-bookworm AS build
|
||||
|
||||
ENV CGO_ENABLED=1
|
||||
|
||||
ARG BUILDPLATFORM
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILD=dev
|
||||
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN apt update && apt install libgdbm-dev
|
||||
RUN git clone https://git.defalsify.org/vise.git go-vise
|
||||
|
||||
WORKDIR /build/services/registration
|
||||
RUN echo "Compiling go-vise files"
|
||||
RUN make VISE_PATH=/build/go-vise -B
|
||||
|
||||
WORKDIR /build
|
||||
RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM"
|
||||
RUN go mod download
|
||||
RUN go build -tags logtrace -o ussd-africastalking -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt update && apt install libgdbm-dev ca-certificates -y
|
||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /service
|
||||
|
||||
COPY --from=build /build/ussd-africastalking .
|
||||
COPY --from=build /build/LICENSE .
|
||||
COPY --from=build /build/README.md .
|
||||
COPY --from=build /build/services ./services
|
||||
COPY --from=build /build/.env.example .
|
||||
RUN mv .env.example .env
|
||||
|
||||
EXPOSE 7123
|
||||
|
||||
CMD ["./ussd-africastalking"]
|
@ -31,6 +31,8 @@ import (
|
||||
var (
|
||||
logg = logging.NewVanilla()
|
||||
scriptDir = path.Join("services", "registration")
|
||||
|
||||
build = "dev"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -115,7 +117,7 @@ func main() {
|
||||
flag.UintVar(&port, "p", initializers.GetEnvUint("PORT", 7123), "http port")
|
||||
flag.Parse()
|
||||
|
||||
logg.Infof("start command", "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
|
||||
logg.Infof("start command", "build", build, "dbdir", dbDir, "resourcedir", resourceDir, "outputsize", size)
|
||||
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, "Database", database)
|
||||
@ -157,6 +159,10 @@ func main() {
|
||||
}
|
||||
|
||||
lhs, err := handlers.NewLocalHandlerService(ctx, pfp, true, dbResource, cfg, rs)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
lhs.SetDataStore(&userdataStore)
|
||||
|
||||
if err != nil {
|
||||
|
119
common/transfer_statements.go
Normal file
119
common/transfer_statements.go
Normal file
@ -0,0 +1,119 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.grassecon.net/urdt/ussd/internal/storage"
|
||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
||||
)
|
||||
|
||||
// TransferMetadata helps organize data fields
|
||||
type TransferMetadata struct {
|
||||
Senders string
|
||||
Recipients string
|
||||
TransferValues string
|
||||
Addresses string
|
||||
TxHashes string
|
||||
Dates string
|
||||
Symbols string
|
||||
Decimals string
|
||||
}
|
||||
|
||||
// ProcessTransfers converts transfers into formatted strings
|
||||
func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetadata {
|
||||
var data TransferMetadata
|
||||
var senders, recipients, transferValues, addresses, txHashes, dates, symbols, decimals []string
|
||||
|
||||
for _, t := range transfers {
|
||||
senders = append(senders, t.Sender)
|
||||
recipients = append(recipients, t.Recipient)
|
||||
|
||||
// Scale down the amount
|
||||
scaledBalance := ScaleDownBalance(t.TransferValue, t.TokenDecimals)
|
||||
transferValues = append(transferValues, scaledBalance)
|
||||
|
||||
addresses = append(addresses, t.ContractAddress)
|
||||
txHashes = append(txHashes, t.TxHash)
|
||||
dates = append(dates, fmt.Sprintf("%s", t.DateBlock))
|
||||
symbols = append(symbols, t.TokenSymbol)
|
||||
decimals = append(decimals, t.TokenDecimals)
|
||||
}
|
||||
|
||||
data.Senders = strings.Join(senders, "\n")
|
||||
data.Recipients = strings.Join(recipients, "\n")
|
||||
data.TransferValues = strings.Join(transferValues, "\n")
|
||||
data.Addresses = strings.Join(addresses, "\n")
|
||||
data.TxHashes = strings.Join(txHashes, "\n")
|
||||
data.Dates = strings.Join(dates, "\n")
|
||||
data.Symbols = strings.Join(symbols, "\n")
|
||||
data.Decimals = strings.Join(decimals, "\n")
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// GetTransferData retrieves and matches transfer data
|
||||
// returns a formatted string of the full transaction/statement
|
||||
func GetTransferData(ctx context.Context, db storage.PrefixDb, publicKey string, index int) (string, error) {
|
||||
keys := []string{"txfrom", "txto", "txval", "txaddr", "txhash", "txdate", "txsym"}
|
||||
data := make(map[string]string)
|
||||
|
||||
for _, key := range keys {
|
||||
value, err := db.Get(ctx, []byte(key))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get %s: %v", key, err)
|
||||
}
|
||||
data[key] = string(value)
|
||||
}
|
||||
|
||||
// Split the data
|
||||
senders := strings.Split(string(data["txfrom"]), "\n")
|
||||
recipients := strings.Split(string(data["txto"]), "\n")
|
||||
values := strings.Split(string(data["txval"]), "\n")
|
||||
addresses := strings.Split(string(data["txaddr"]), "\n")
|
||||
hashes := strings.Split(string(data["txhash"]), "\n")
|
||||
dates := strings.Split(string(data["txdate"]), "\n")
|
||||
syms := strings.Split(string(data["txsym"]), "\n")
|
||||
|
||||
// Check if index is within range
|
||||
if index < 1 || index > len(senders) {
|
||||
return "", fmt.Errorf("transaction not found: index %d out of range", index)
|
||||
}
|
||||
|
||||
// Adjust for 0-based indexing
|
||||
i := index - 1
|
||||
transactionType := "received"
|
||||
party := fmt.Sprintf("from: %s", strings.TrimSpace(senders[i]))
|
||||
if strings.TrimSpace(senders[i]) == publicKey {
|
||||
transactionType = "sent"
|
||||
party = fmt.Sprintf("to: %s", strings.TrimSpace(recipients[i]))
|
||||
}
|
||||
|
||||
formattedDate := formatDate(strings.TrimSpace(dates[i]))
|
||||
|
||||
// Build the full transaction detail
|
||||
detail := fmt.Sprintf(
|
||||
"%s %s %s\n%s\ncontract address: %s\ntxhash: %s\ndate: %s",
|
||||
transactionType,
|
||||
strings.TrimSpace(values[i]),
|
||||
strings.TrimSpace(syms[i]),
|
||||
party,
|
||||
strings.TrimSpace(addresses[i]),
|
||||
strings.TrimSpace(hashes[i]),
|
||||
formattedDate,
|
||||
)
|
||||
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
// Helper function to format date in desired output
|
||||
func formatDate(dateStr string) string {
|
||||
parsedDate, err := time.Parse("2006-01-02 15:04:05 -0700 MST", dateStr)
|
||||
if err != nil {
|
||||
fmt.Println("Error parsing date:", err)
|
||||
return ""
|
||||
}
|
||||
return parsedDate.Format("2006-01-02 03:04:05 PM")
|
||||
}
|
3
dev/dialoguss/sample_user.yaml
Normal file
3
dev/dialoguss/sample_user.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
url: http://localhost:7123
|
||||
dial: "*384*96#"
|
||||
phoneNumber: +254722123456
|
21
dev/docker-compose.yaml
Normal file
21
dev/docker-compose.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
services:
|
||||
ussd-pg-store:
|
||||
image: postgres:17-alpine
|
||||
restart: unless-stopped
|
||||
user: postgres
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_USER=postgres
|
||||
volumes:
|
||||
- ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
|
||||
- ussd-pg:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
ussd-pg:
|
||||
driver: local
|
1
dev/init_db.sql
Normal file
1
dev/init_db.sql
Normal file
@ -0,0 +1 @@
|
||||
CREATE DATABASE urdt_ussd;
|
@ -103,7 +103,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
|
||||
ls.DbRs.AddLocalFunc("verify_new_pin", ussdHandlers.VerifyNewPin)
|
||||
ls.DbRs.AddLocalFunc("confirm_pin_change", ussdHandlers.ConfirmPinChange)
|
||||
ls.DbRs.AddLocalFunc("quit_with_help", ussdHandlers.QuitWithHelp)
|
||||
ls.DbRs.AddLocalFunc("fetch_custodial_balances", ussdHandlers.FetchCustodialBalances)
|
||||
ls.DbRs.AddLocalFunc("fetch_community_balance", ussdHandlers.FetchCommunityBalance)
|
||||
ls.DbRs.AddLocalFunc("set_default_voucher", ussdHandlers.SetDefaultVoucher)
|
||||
ls.DbRs.AddLocalFunc("check_vouchers", ussdHandlers.CheckVouchers)
|
||||
ls.DbRs.AddLocalFunc("get_vouchers", ussdHandlers.GetVoucherList)
|
||||
@ -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
|
||||
}
|
||||
|
@ -822,42 +822,14 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
func (h *Handlers) FetchCommunityBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
sessionId, ok := ctx.Value("SessionId").(string)
|
||||
if !ok {
|
||||
return res, fmt.Errorf("missing session")
|
||||
}
|
||||
symbol, _ := h.st.Where()
|
||||
balanceType := strings.Split(symbol, "_")[0]
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
balanceResponse, err := h.accountService.CheckBalance(ctx, string(publicKey))
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_api_error)
|
||||
return res, nil
|
||||
}
|
||||
res.FlagReset = append(res.FlagReset, flag_api_error)
|
||||
|
||||
balance := balanceResponse.Balance
|
||||
|
||||
switch balanceType {
|
||||
case "my":
|
||||
res.Content = fmt.Sprintf("Your balance is %s", balance)
|
||||
case "community":
|
||||
res.Content = fmt.Sprintf("Your community balance is %s", balance)
|
||||
default:
|
||||
break
|
||||
}
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
//TODO:
|
||||
//Check if the address is a community account,if then,get the actual balance
|
||||
res.Content = l.Get("Community Balance: 0.00")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@ -1661,3 +1633,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
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package ussd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
@ -1773,58 +1772,43 @@ func TestConfirmPin(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchCustodialBalances(t *testing.T) {
|
||||
fm, err := NewFlagManager(flagsPath)
|
||||
if err != nil {
|
||||
t.Logf(err.Error())
|
||||
}
|
||||
flag_api_error, _ := fm.GetFlag("flag_api_call_error")
|
||||
func TestFetchCommunityBalance(t *testing.T) {
|
||||
|
||||
// Define test data
|
||||
sessionId := "session123"
|
||||
publicKey := "0X13242618721"
|
||||
|
||||
ctx, store := InitializeTestStore(t)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
|
||||
err = store.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(publicKey))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
balanceResponse *models.BalanceResult
|
||||
languageCode string
|
||||
expectedResult resource.Result
|
||||
}{
|
||||
{
|
||||
name: "Test when fetch custodial balances is not a success",
|
||||
balanceResponse: &models.BalanceResult{
|
||||
Balance: "0.003 CELO",
|
||||
Nonce: json.Number("0"),
|
||||
},
|
||||
name: "Test community balance content when language is english",
|
||||
expectedResult: resource.Result{
|
||||
FlagReset: []uint32{flag_api_error},
|
||||
Content: "Community Balance: 0.00",
|
||||
},
|
||||
languageCode: "eng",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
mockAccountService := new(mocks.MockAccountService)
|
||||
mockState := state.NewState(16)
|
||||
|
||||
h := &Handlers{
|
||||
userdataStore: store,
|
||||
flagManager: fm.parser,
|
||||
st: mockState,
|
||||
accountService: mockAccountService,
|
||||
}
|
||||
|
||||
// Set up the expected behavior of the mock
|
||||
mockAccountService.On("CheckBalance", string(publicKey)).Return(tt.balanceResponse, nil)
|
||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
||||
ctx = context.WithValue(ctx, "Language", lang.Language{
|
||||
Code: tt.languageCode,
|
||||
})
|
||||
|
||||
// Call the method
|
||||
res, _ := h.FetchCustodialBalances(ctx, "fetch_custodial_balances", []byte(""))
|
||||
res, _ := h.FetchCommunityBalance(ctx, "fetch_community_balance", []byte(""))
|
||||
|
||||
//Assert that the result set to content is what was expected
|
||||
assert.Equal(t, res, tt.expectedResult, "Result should match expected result")
|
||||
|
@ -41,10 +41,13 @@ func buildConnStr() string {
|
||||
dbName := initializers.GetEnv("DB_NAME", "")
|
||||
port := initializers.GetEnv("DB_PORT", "5432")
|
||||
|
||||
return fmt.Sprintf(
|
||||
connString := fmt.Sprintf(
|
||||
"postgres://%s:%s@%s:%s/%s",
|
||||
user, password, host, port, dbName,
|
||||
)
|
||||
logg.Debugf("pg conn string", "conn", connString)
|
||||
|
||||
return connString
|
||||
}
|
||||
|
||||
func NewMenuStorageService(dbDir string, resourceDir string) *MenuStorageService {
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
package testtag
|
||||
|
||||
import "git.grassecon.net/urdt/ussd/internal/handlers/server"
|
||||
import "git.grassecon.net/urdt/ussd/remote"
|
||||
|
||||
var (
|
||||
AccountService server.AccountServiceInterface
|
||||
AccountService remote.AccountServiceInterface
|
||||
)
|
||||
|
@ -103,7 +103,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Your balance is 0.003 CELO\n0:Back\n9:Quit"
|
||||
"expectedContent": "Balance: {balance}\n\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
@ -149,7 +149,7 @@
|
||||
},
|
||||
{
|
||||
"input": "1234",
|
||||
"expectedContent": "Your community balance is 0.003 CELO\n0:Back\n9:Quit"
|
||||
"expectedContent": "{balance}\n0:Back\n9:Quit"
|
||||
},
|
||||
{
|
||||
"input": "0",
|
||||
|
1
services/registration/check_statement
Normal file
1
services/registration/check_statement
Normal file
@ -0,0 +1 @@
|
||||
Please enter your PIN to view statement:
|
12
services/registration/check_statement.vis
Normal file
12
services/registration/check_statement.vis
Normal file
@ -0,0 +1,12 @@
|
||||
LOAD check_transactions 0
|
||||
RELOAD check_transactions
|
||||
CATCH no_transfers flag_no_transfers 1
|
||||
LOAD authorize_account 6
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
HALT
|
||||
RELOAD authorize_account
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
INCMP _ 0
|
||||
INCMP quit 9
|
||||
INCMP transactions *
|
1
services/registration/check_statement_swa
Normal file
1
services/registration/check_statement_swa
Normal file
@ -0,0 +1 @@
|
||||
Tafadhali weka PIN yako kuona taarifa ya matumizi:
|
@ -1 +1 @@
|
||||
Salio la kikundi
|
||||
{{.fetch_community_balance}}
|
@ -1 +1 @@
|
||||
{{.fetch_custodial_balances}}
|
||||
{{.fetch_community_balance}}
|
@ -1,7 +1,7 @@
|
||||
LOAD reset_incorrect 6
|
||||
LOAD fetch_custodial_balances 0
|
||||
LOAD fetch_community_balance 0
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
MAP fetch_custodial_balances
|
||||
MAP fetch_community_balance
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH pin_entry flag_account_authorized 0
|
||||
MOUT back 0
|
||||
|
@ -21,3 +21,6 @@ msgstr "Ombi lako la kumwalika %s kwa matandao wa Sarafu limetumwa."
|
||||
|
||||
msgid "Your request failed. Please try again later."
|
||||
msgstr "Ombi lako halikufaulu. Tafadhali jaribu tena baadaye."
|
||||
|
||||
msgid "Community Balance: 0.00"
|
||||
msgid "Salio la Kikundi: 0.00"
|
||||
|
@ -11,5 +11,6 @@ INCMP main 0
|
||||
INCMP edit_profile 1
|
||||
INCMP change_language 2
|
||||
INCMP balances 3
|
||||
INCMP check_statement 4
|
||||
INCMP pin_management 5
|
||||
INCMP address 6
|
||||
|
@ -1 +1 @@
|
||||
{{.fetch_custodial_balances}}
|
||||
{{.check_balance}}
|
@ -1,7 +1,7 @@
|
||||
LOAD reset_incorrect 6
|
||||
LOAD fetch_custodial_balances 0
|
||||
LOAD check_balance 0
|
||||
CATCH api_failure flag_api_call_error 1
|
||||
MAP fetch_custodial_balances
|
||||
MAP check_balance
|
||||
CATCH incorrect_pin flag_incorrect_pin 1
|
||||
CATCH pin_entry flag_account_authorized 0
|
||||
MOUT back 0
|
||||
|
1
services/registration/no_transfers
Normal file
1
services/registration/no_transfers
Normal file
@ -0,0 +1 @@
|
||||
No transfers history
|
5
services/registration/no_transfers.vis
Normal file
5
services/registration/no_transfers.vis
Normal file
@ -0,0 +1,5 @@
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP ^ 0
|
||||
INCMP quit 9
|
1
services/registration/no_transfers_swa
Normal file
1
services/registration/no_transfers_swa
Normal file
@ -0,0 +1 @@
|
||||
Hakuna historia kwa akaunti yako
|
@ -19,3 +19,5 @@ flag,flag_api_call_error,25,this is set when communication to an external servic
|
||||
flag,flag_no_active_voucher,26,this is set when a user does not have an active voucher
|
||||
flag,flag_admin_privilege,27,this is set when a user has admin privileges.
|
||||
flag,flag_unregistered_number,28,this is set when an unregistered phonenumber tries to perform an action
|
||||
flag,flag_no_transfers,29,this is set when a user does not have any transactions
|
||||
flag,flag_incorrect_statement,30,this is set when the selected statement is invalid
|
||||
|
|
1
services/registration/transactions
Normal file
1
services/registration/transactions
Normal file
@ -0,0 +1 @@
|
||||
{{.get_transactions}}
|
15
services/registration/transactions.vis
Normal file
15
services/registration/transactions.vis
Normal file
@ -0,0 +1,15 @@
|
||||
LOAD get_transactions 0
|
||||
MAP get_transactions
|
||||
MOUT back 0
|
||||
MOUT quit 99
|
||||
MNEXT next 11
|
||||
MPREV prev 22
|
||||
HALT
|
||||
LOAD view_statement 0
|
||||
RELOAD view_statement
|
||||
CATCH . flag_incorrect_statement 1
|
||||
INCMP ^ 0
|
||||
INCMP quit 99
|
||||
INCMP > 11
|
||||
INCMP < 22
|
||||
INCMP view_statement *
|
1
services/registration/transactions_swa
Normal file
1
services/registration/transactions_swa
Normal file
@ -0,0 +1 @@
|
||||
{{.get_transactions}}
|
1
services/registration/view_statement
Normal file
1
services/registration/view_statement
Normal file
@ -0,0 +1 @@
|
||||
{{.view_statement}}
|
10
services/registration/view_statement.vis
Normal file
10
services/registration/view_statement.vis
Normal file
@ -0,0 +1,10 @@
|
||||
MAP view_statement
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
MNEXT next 11
|
||||
MPREV prev 22
|
||||
HALT
|
||||
INCMP _ 0
|
||||
INCMP quit 9
|
||||
INCMP > 11
|
||||
INCMP < 22
|
Loading…
Reference in New Issue
Block a user