Compare commits
11 Commits
98bc2dbac1
...
9b8c5a021b
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b8c5a021b | |||
| 0da64b8565 | |||
| 04293d5476 | |||
| 15bf7dc956 | |||
| 0fd1f43602 | |||
| 8433bda6f6 | |||
| 010696e153 | |||
| a5cdd72480 | |||
| 06d6ab8692 | |||
| 45a6ef4066 | |||
| 7ae4a6fd5d |
@ -32,3 +32,8 @@ INCLUDE_STABLES_PARAM=false
|
||||
|
||||
#Mpesa address
|
||||
DEFAULT_MPESA_ADDRESS=0x48a953cA5cf5298bc6f6Af3C608351f537AAcb9e
|
||||
MPESA_RATE=129.5
|
||||
MIN_MPESA_SEND_AMOUNT=100
|
||||
MAX_MPESA_SEND_AMOUNT=250000
|
||||
MPESA_SEND_RATE=130.2
|
||||
DEFAULT_MPESA_ASSET=cUSD
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
apiconfig "git.grassecon.net/grassrootseconomics/sarafu-api/config"
|
||||
@ -91,3 +92,43 @@ func DefaultPoolSymbol() string {
|
||||
func DefaultMpesaAddress() string {
|
||||
return env.GetEnv("DEFAULT_MPESA_ADDRESS", "")
|
||||
}
|
||||
|
||||
func MpesaRate() float64 {
|
||||
v := env.GetEnv("MPESA_RATE", "129.5")
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 129.5 // fallback default
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func MinMpesaSendAmount() float64 {
|
||||
v := env.GetEnv("MIN_MPESA_SEND_AMOUNT", "100")
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 100 // fallback
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func MaxMpesaSendAmount() float64 {
|
||||
v := env.GetEnv("MAX_MPESA_SEND_AMOUNT", "250000")
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 250000 // fallback
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func MpesaSendRate() float64 {
|
||||
v := env.GetEnv("MPESA_SEND_RATE", "130.2")
|
||||
f, err := strconv.ParseFloat(v, 64)
|
||||
if err != nil {
|
||||
return 130.2 // fallback default
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func DefaultMpesaAsset() string {
|
||||
return env.GetEnv("DEFAULT_MPESA_ASSET", "")
|
||||
}
|
||||
|
||||
2
go.mod
2
go.mod
@ -4,7 +4,7 @@ go 1.23.4
|
||||
|
||||
require (
|
||||
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20251127132814-8ceadabbc215
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20251028083421-fe897cca84f2
|
||||
git.grassecon.net/grassrootseconomics/visedriver v0.9.0-beta.2.0.20250408094335-e2d1f65bb306
|
||||
git.grassecon.net/grassrootseconomics/visedriver-africastalking v0.0.0-20250129070628-5a539172c694
|
||||
|
||||
2
go.sum
2
go.sum
@ -2,6 +2,8 @@ git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66 h1:hmtb2Q3lHxq+S
|
||||
git.defalsify.org/vise.git v0.3.2-0.20250528124150-03bf7bfc1b66/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e h1:DcC9qkNl9ny3hxQmsMK6W81+5R/j4ZwYUbvewMI/rlc=
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20250417111317-2953f4c2f32e/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20251127132814-8ceadabbc215 h1:cxWmd3WG3iVEqP6qG8ZeQRa7Ujno3rSKz3YXjZnmTEY=
|
||||
git.grassecon.net/grassrootseconomics/common v0.9.0-beta.1.0.20251127132814-8ceadabbc215/go.mod h1:wgQJZGIS6QuNLHqDhcsvehsbn5PvgV7aziRebMnJi60=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623063234-c1797e7a32b5 h1:VnRe01kHkZUBK/QjE7iV6gElSqSwQnAkWV3yCHtuYrI=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623063234-c1797e7a32b5/go.mod h1:H97hR+VOnZvR5BiGVb0ScCPwH/IoKBOlKM+yrQNVpq0=
|
||||
git.grassecon.net/grassrootseconomics/sarafu-api v0.9.0-beta.1.0.20250623070026-d945964b0b46 h1:0+XkSRe7XSHa9WHXKpGPuC0myDszjchr4syH006lQ28=
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"git.defalsify.org/vise.git/resource"
|
||||
"git.grassecon.net/grassrootseconomics/common/hex"
|
||||
"git.grassecon.net/grassrootseconomics/common/phone"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/config"
|
||||
"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
|
||||
storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
|
||||
@ -44,7 +45,7 @@ func (h *MenuHandlers) GetMpesaMaxLimit(ctx context.Context, sym string, input [
|
||||
return res, err
|
||||
}
|
||||
|
||||
const rate = 129.5
|
||||
rate := config.MpesaRate()
|
||||
txType := "swap"
|
||||
mpesaAddress := config.DefaultMpesaAddress()
|
||||
|
||||
@ -191,7 +192,7 @@ func (h *MenuHandlers) GetMpesaPreview(ctx context.Context, sym string, input []
|
||||
l.AddDomain("default")
|
||||
|
||||
userStore := h.userdataStore
|
||||
const rate = 129.5
|
||||
rate := config.MpesaRate()
|
||||
|
||||
// Input in Ksh
|
||||
kshAmount, err := strconv.ParseFloat(inputStr, 64)
|
||||
@ -369,7 +370,7 @@ func (h *MenuHandlers) InitiateGetMpesa(ctx context.Context, sym string, input [
|
||||
|
||||
logg.InfoCtxf(ctx, "TokenTransfer normal", "trackingId", tokenTransfer.TrackingId)
|
||||
|
||||
res.Content = l.Get("Your request has been sent. You will receive %s ksh.", data.TemporaryValue)
|
||||
res.Content = l.Get("Your request has been sent. You will receive %s ksh", data.TemporaryValue)
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
return res, nil
|
||||
@ -417,7 +418,155 @@ func (h *MenuHandlers) InitiateGetMpesa(ctx context.Context, sym string, input [
|
||||
|
||||
logg.InfoCtxf(ctx, "final TokenTransfer after swap", "trackingId", tokenTransfer.TrackingId)
|
||||
|
||||
res.Content = l.Get("Your request has been sent. You will receive %s ksh.", finalKshStr)
|
||||
res.Content = l.Get("Your request has been sent. You will receive %s ksh", finalKshStr)
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SendMpesaMinLimit returns the min amount from the config
|
||||
func (h *MenuHandlers) SendMpesaMinLimit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
|
||||
var res resource.Result
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" || inputStr == "9" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Fetch min amount from config/env
|
||||
min := config.MinMpesaSendAmount()
|
||||
|
||||
// Convert to string
|
||||
ksh := fmt.Sprintf("%f", min)
|
||||
|
||||
// Format (e.g., 100.0 -> 100)
|
||||
kshFormatted, _ := store.TruncateDecimalString(ksh, 0)
|
||||
|
||||
res.Content = l.Get(
|
||||
"Enter the amount of Mpesa to send: (Minimum %s Ksh)\n",
|
||||
kshFormatted,
|
||||
)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SendMpesaPreview displays the send mpesa preview and estimates
|
||||
func (h *MenuHandlers) SendMpesaPreview(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")
|
||||
}
|
||||
|
||||
// INPUT IN Ksh
|
||||
inputStr := string(input)
|
||||
if inputStr == "0" || inputStr == "9" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
flag_invalid_amount, _ := h.flagManager.GetFlag("flag_invalid_amount")
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
userStore := h.userdataStore
|
||||
sendRate := config.MpesaSendRate()
|
||||
|
||||
// Input in Ksh
|
||||
kshAmount, err := strconv.ParseFloat(inputStr, 64)
|
||||
if err != nil {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = inputStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
min := config.MinMpesaSendAmount()
|
||||
max := config.MaxMpesaSendAmount()
|
||||
|
||||
if kshAmount > max || kshAmount < min {
|
||||
res.FlagSet = append(res.FlagSet, flag_invalid_amount)
|
||||
res.Content = inputStr
|
||||
return res, nil
|
||||
}
|
||||
|
||||
res.FlagReset = append(res.FlagReset, flag_invalid_amount)
|
||||
|
||||
// store the user's raw input amount
|
||||
err = userStore.WriteEntry(ctx, sessionId, storedb.DATA_AMOUNT, []byte(inputStr))
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to write amount inputStr entry with", "key", storedb.DATA_AMOUNT, "value", inputStr, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
estimateValue := kshAmount / sendRate
|
||||
estimateStr := fmt.Sprintf("%f", estimateValue)
|
||||
estimateFormatted, _ := store.TruncateDecimalString(estimateStr, 0)
|
||||
|
||||
res.Content = l.Get(
|
||||
"You will get a prompt for your M-Pesa PIN shortly to send %s ksh and receive %s cUSD",
|
||||
inputStr, estimateFormatted,
|
||||
)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// InitiateSendMpesa calls the trigger-onram API to initiate the purchase
|
||||
func (h *MenuHandlers) InitiateSendMpesa(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")
|
||||
flag_api_call_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
|
||||
code := codeFromCtx(ctx)
|
||||
l := gotext.NewLocale(translationDir, code)
|
||||
l.AddDomain("default")
|
||||
|
||||
userStore := h.userdataStore
|
||||
|
||||
publicKey, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read publicKey entry", "key", storedb.DATA_PUBLIC_KEY, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
phoneNumber, err := phone.FormatToLocalPhoneNumber(sessionId)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed on FormatToLocalPhoneNumber", "session-id", sessionId, "error", err)
|
||||
res.FlagSet = append(res.FlagSet, flag_api_call_error)
|
||||
res.Content = l.Get("Your request failed. Please try again later.")
|
||||
return res, nil
|
||||
}
|
||||
|
||||
defaultAsset := config.DefaultMpesaAsset()
|
||||
|
||||
amount, err := userStore.ReadEntry(ctx, sessionId, storedb.DATA_AMOUNT)
|
||||
if err != nil {
|
||||
logg.ErrorCtxf(ctx, "failed to read amount entry", "key", storedb.DATA_AMOUNT, "error", err)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Call the trigger onramp API
|
||||
triggerOnramp, err := h.accountService.MpesaTriggerOnramp(ctx, string(publicKey), phoneNumber, defaultAsset, string(amount))
|
||||
if err != nil {
|
||||
flag_api_call_error, _ := h.flagManager.GetFlag("flag_api_call_error")
|
||||
res.FlagSet = append(res.FlagSet, flag_api_call_error)
|
||||
res.Content = l.Get("Your request failed. Please try again later.")
|
||||
logg.ErrorCtxf(ctx, "failed on MpesaTriggerOnramp", "error", err)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
logg.InfoCtxf(ctx, "MpesaTriggerOnramp", "transactionCode", triggerOnramp.TransactionCode)
|
||||
|
||||
res.Content = l.Get("Your request has been sent. Thank you for using Sarafu")
|
||||
res.FlagReset = append(res.FlagReset, flag_account_authorized)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
@ -143,6 +143,9 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountService)
|
||||
ls.DbRs.AddLocalFunc("get_mpesa_max_limit", appHandlers.GetMpesaMaxLimit)
|
||||
ls.DbRs.AddLocalFunc("get_mpesa_preview", appHandlers.GetMpesaPreview)
|
||||
ls.DbRs.AddLocalFunc("initiate_get_mpesa", appHandlers.InitiateGetMpesa)
|
||||
ls.DbRs.AddLocalFunc("send_mpesa_min_limit", appHandlers.SendMpesaMinLimit)
|
||||
ls.DbRs.AddLocalFunc("send_mpesa_preview", appHandlers.SendMpesaPreview)
|
||||
ls.DbRs.AddLocalFunc("initiate_send_mpesa", appHandlers.InitiateSendMpesa)
|
||||
|
||||
ls.first = appHandlers.Init
|
||||
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
LOAD get_mpesa_max_limit 0
|
||||
RELOAD get_mpesa_max_limit
|
||||
MAP get_mpesa_max_limit
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP _ 0
|
||||
INCMP quit 9
|
||||
INCMP get_mpesa_confirmation *
|
||||
|
||||
@ -1 +1 @@
|
||||
Get Mpesa
|
||||
Get M-Pesa
|
||||
@ -1 +1 @@
|
||||
Pokea Mpesa
|
||||
Pokea M-Pesa
|
||||
4
services/registration/initiate_send_mpesa.vis
Normal file
4
services/registration/initiate_send_mpesa.vis
Normal file
@ -0,0 +1,4 @@
|
||||
LOAD reset_incorrect_pin 6
|
||||
CATCH _ flag_account_authorized 0
|
||||
LOAD initiate_send_mpesa 0
|
||||
HALT
|
||||
1
services/registration/invalid_send_mpesa_amount
Normal file
1
services/registration/invalid_send_mpesa_amount
Normal file
@ -0,0 +1 @@
|
||||
Amount {{.send_mpesa_preview}} is invalid, please try again:
|
||||
6
services/registration/invalid_send_mpesa_amount.vis
Normal file
6
services/registration/invalid_send_mpesa_amount.vis
Normal file
@ -0,0 +1,6 @@
|
||||
MAP send_mpesa_preview
|
||||
MOUT retry 1
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP ^ 1
|
||||
INCMP quit 9
|
||||
1
services/registration/invalid_send_mpesa_amount_swa
Normal file
1
services/registration/invalid_send_mpesa_amount_swa
Normal file
@ -0,0 +1 @@
|
||||
Kiwango {{.send_mpesa_preview}} sio sahihi, tafadhali weka tena:
|
||||
@ -55,8 +55,20 @@ msgstr "Kiwango kinachopatikana: %s %s\n(Unaweza kubadilisha hadi %s %s -> %s %s
|
||||
msgid "%s will receive %s %s"
|
||||
msgstr "%s atapokea %s %s"
|
||||
|
||||
msgid "Enter the amount of Mpesa to get: (Max %s Ksh)\n"
|
||||
msgstr "Weka kiasi cha Moesa cha kupata: (Kikomo %s Ksh)\n"
|
||||
msgid "Enter the amount of M-Pesa to get: (Max %s Ksh)\n"
|
||||
msgstr "Weka kiasi cha M-Pesa cha kupata: (Kikomo %s Ksh)\n"
|
||||
|
||||
msgid "You are sending %s %s in order to receive %s ksh"
|
||||
msgstr "Unatuma %s %s ili upoke %s ksh"
|
||||
msgstr "Unatuma %s %s ili upoke %s ksh"
|
||||
|
||||
msgid "Your request has been sent. You will receive %s ksh"
|
||||
msgstr "Ombi lako limetumwa. Utapokea %s ksh"
|
||||
|
||||
msgid "Enter the amount of M-Pesa to send: (Minimum %s Ksh)\n"
|
||||
msgstr "Weka kiasi cha M-Pesa cha kutuma: (Kima cha chini %s Ksh)\n"
|
||||
|
||||
msgid "You will get a prompt for your M-Pesa PIN shortly to send %s ksh and receive %s cUSD"
|
||||
msgstr "Utapokea kidokezo cha PIN yako ya M-Pesa hivi karibuni kutuma %s ksh na kupokea %s cUSD"
|
||||
|
||||
msgid "Your request has been sent. Thank you for using Sarafu"
|
||||
msgstr "Ombi lako limetumwa. Asante kwa kutumia huduma ya Sarafu"
|
||||
@ -1 +1 @@
|
||||
MPesa
|
||||
M-Pesa
|
||||
1
services/registration/send_mpesa
Normal file
1
services/registration/send_mpesa
Normal file
@ -0,0 +1 @@
|
||||
{{.send_mpesa_min_limit}}
|
||||
9
services/registration/send_mpesa.vis
Normal file
9
services/registration/send_mpesa.vis
Normal file
@ -0,0 +1,9 @@
|
||||
LOAD send_mpesa_min_limit 0
|
||||
RELOAD send_mpesa_min_limit
|
||||
MAP send_mpesa_min_limit
|
||||
MOUT back 0
|
||||
MOUT quit 9
|
||||
HALT
|
||||
INCMP _ 0
|
||||
INCMP quit 9
|
||||
INCMP send_mpesa_confirmation *
|
||||
3
services/registration/send_mpesa_confirmation
Normal file
3
services/registration/send_mpesa_confirmation
Normal file
@ -0,0 +1,3 @@
|
||||
{{.send_mpesa_preview}}
|
||||
|
||||
Please enter your account PIN to confirm:
|
||||
12
services/registration/send_mpesa_confirmation.vis
Normal file
12
services/registration/send_mpesa_confirmation.vis
Normal file
@ -0,0 +1,12 @@
|
||||
LOAD send_mpesa_preview 0
|
||||
MAP send_mpesa_preview
|
||||
CATCH invalid_send_mpesa_amount flag_invalid_amount 1
|
||||
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 initiate_send_mpesa *
|
||||
3
services/registration/send_mpesa_confirmation_swa
Normal file
3
services/registration/send_mpesa_confirmation_swa
Normal file
@ -0,0 +1,3 @@
|
||||
{{.send_mpesa_preview}}
|
||||
|
||||
Tafadhali weka PIN ya akaunti yako kudhibitisha:
|
||||
1
services/registration/send_mpesa_menu
Normal file
1
services/registration/send_mpesa_menu
Normal file
@ -0,0 +1 @@
|
||||
Send M-Pesa
|
||||
Loading…
Reference in New Issue
Block a user