Documentation
¶
Overview ¶
Package ussd is the interactive USSD application: the menu-driven flow a mobile subscriber walks through to register, request a loan, pick a payout method, repay, and manage their PIN.
The package is layered. USSDService is the outermost entry point — it holds the registered USSDProvider transports (one per telecom gateway) and dispatches an incoming request to USSDHandler. The handler owns the application logic: it resolves the Session and routes each input with an explicit switch on the session's current menu.
The MenuRegistry is a rendering store, not a router: it supplies a screen's localized title and options, and nothing more.
There is no session-level login. A caller is identified by the MSISDN the gateway reports, and authorization happens at the screens that move money, where the handler calls PINService.VerifyPIN — loan confirmation and the repayment gate. A new screen that moves money needs its own VerifyPIN call; nothing gates it implicitly.
Session and menus ¶
SessionManager persists session state in Redis with a short TTL (default 5 minutes). A Session carries the current menu, the menu history stack, the chosen language, and a free-form Data bag for cross-screen state (loan amount, payout method, national ID, etc.). Menus are registered once at startup. Handlers return the gateway's wire prefix directly: "CON " keeps the session open, "END " releases it.
MenuRegistry holds the menu graph. Menus are built with MenuBuilder and grouped into presets — StandardLoanMenuPreset wires the full registration, loan, repayment, PIN, and security-question flow. Each Menu carries language-keyed Titles and Options so the same graph renders in any supported language.
Localization ¶
InMemoryLocalizer stores translations keyed by language and message key, with fallback to the default language when a translation is missing.
Network mapping ¶
NetworkMapper resolves a telco's MCC+MNC code (e.g. "63902") to the corresponding mobile-money network (M-Pesa, Airtel Money, etc.) and ISO country. It is used during registration to pre-fill the user's network and country from the carrier-reported code.
Ports ¶
The handler depends on four narrow interfaces defined in this package — UserService, LoanService, RateService, PINService — plus a contracts.AccountNotifier for side-effect SMS. Each port is satisfied by an adapter in pkg/mobile/ussd/adapters, which translates between the USSD request/response shapes and the underlying user, account, loan, off-ramp, and Stellar services. The handler never imports those services directly.
Providers ¶
USSDProvider is the transport contract: ParseRequest turns a gateway's HTTP form post into a normalized USSDRequest, FormatResponse turns a USSDResponse back into the gateway's expected shape, and ValidateRequest gates entry. Concrete transports live under providers/ (today: providers/africastalking).
For the architecture diagram, request lifecycle, and file/subpackage map, see README.md.
Index ¶
- Constants
- Variables
- func Format(language, key string, args ...any) string
- func GetLocalizedMessage(language, key string) string
- type BioUpdate
- type CarrierRepaymentPrompter
- type CustomMenuPreset
- type HandlerDeps
- type InMemoryLocalizer
- func (l *InMemoryLocalizer) AddTranslation(key, language, message string)
- func (l *InMemoryLocalizer) Get(language, key string) string
- func (l *InMemoryLocalizer) GetWithDefault(language, key, defaultMsg string) string
- func (l *InMemoryLocalizer) HasKey(key string) bool
- func (l *InMemoryLocalizer) LoadStandardTranslations()
- type LoanApproval
- type LoanProductConfig
- type LoanRequest
- type LoanService
- type Localizer
- type Menu
- type MenuBuilder
- type MenuOption
- type MenuPreset
- type MenuRegistry
- type NetworkMapper
- type NetworkMapping
- type PINService
- type RateService
- type RegisterUserRequest
- type RepaymentPrompter
- type RepaymentQuote
- type Session
- type SessionManager
- func (sm *SessionManager) CreateSession(ctx context.Context, sessionID, phoneNumber, serviceCode, networkCode string) (*Session, error)
- func (sm *SessionManager) DeleteSession(ctx context.Context, sessionID string) error
- func (sm *SessionManager) ExtendSession(ctx context.Context, sessionID string) error
- func (sm *SessionManager) GetData(ctx context.Context, sessionID, key string) (any, error)
- func (sm *SessionManager) GetOrCreateSession(ctx context.Context, sessionID, phoneNumber, serviceCode, networkCode string) (*Session, error)
- func (sm *SessionManager) GetSession(ctx context.Context, sessionID string) (*Session, error)
- func (sm *SessionManager) GoBack(ctx context.Context, sessionID string) error
- func (sm *SessionManager) NavigateToMenu(ctx context.Context, sessionID, menu string) error
- func (sm *SessionManager) SaveSession(ctx context.Context, session *Session) error
- func (sm *SessionManager) SetData(ctx context.Context, sessionID, key string, value any) error
- func (sm *SessionManager) SetLanguage(ctx context.Context, sessionID, language string) error
- func (sm *SessionManager) SetUserID(ctx context.Context, sessionID, userID string) error
- func (sm *SessionManager) UpdateSession(ctx context.Context, sessionID string, updates func(*Session)) error
- type SimplifiedMenuPreset
- type StandardLoanMenuPreset
- type TranslationBuilder
- type USSDHandler
- type USSDProvider
- type USSDRequest
- type USSDResponse
- type USSDService
- func (s *USSDService) DeleteAllProviders() error
- func (s *USSDService) DeleteProvider(name string) error
- func (s *USSDService) GetProvider(name string) (USSDProvider, error)
- func (s *USSDService) GetProviders() map[string]USSDProvider
- func (s *USSDService) HandleRequest(ctx context.Context, providerName string, data map[string]string) (any, error)
- func (s *USSDService) RegisterProvider(name string, provider USSDProvider)
- type UserService
Constants ¶
const ( MinMoneyGramDepositStroops int64 = int64(moneygram.MinDepositUSD * 1e7) MaxMoneyGramDepositStroops int64 = int64(moneygram.MaxDepositUSD * 1e7) )
MoneyGram's production on-ramp bounds, in stroops.
Variables ¶
var ErrSessionNotFound = errors.New("session not found")
ErrSessionNotFound marks a session that is genuinely absent from the cache, as distinct from a cache that cannot be read. Both end the borrower's turn, but only one of them is an outage.
var NetworkMappings = map[string]NetworkMapping{
"63902": {MobileNetworkCode: "63902", MomoNetworkCode: "MPESA", MomoNetworkName: "M-Pesa", TelcoName: "Safaricom Kenya", Country: "KE"},
"63903": {MobileNetworkCode: "63903", MomoNetworkCode: "AIRTEL", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Kenya", Country: "KE"},
"63907": {MobileNetworkCode: "63907", MomoNetworkCode: "TKASH", MomoNetworkName: "T-Kash", TelcoName: "Orange Kenya", Country: "KE"},
"63999": {MobileNetworkCode: "63999", MomoNetworkCode: "EQUITEL", MomoNetworkName: "Equitel", TelcoName: "Equitel Kenya", Country: "KE"},
"64101": {MobileNetworkCode: "64101", MomoNetworkCode: "AIRTEL_UG", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Uganda", Country: "UG"},
"64110": {MobileNetworkCode: "64110", MomoNetworkCode: "MTN_UG", MomoNetworkName: "MTN Mobile Money", TelcoName: "MTN Uganda", Country: "UG"},
"64114": {MobileNetworkCode: "64114", MomoNetworkCode: "AFRICELL_UG", MomoNetworkName: "Africell Money", TelcoName: "Africell Uganda", Country: "UG"},
"64002": {MobileNetworkCode: "64002", MomoNetworkCode: "TIGO_TZ", MomoNetworkName: "Tigo Pesa", TelcoName: "Tigo Tanzania", Country: "TZ"},
"64004": {MobileNetworkCode: "64004", MomoNetworkCode: "VODACOM_TZ", MomoNetworkName: "M-Pesa", TelcoName: "Vodacom Tanzania", Country: "TZ"},
"64005": {MobileNetworkCode: "64005", MomoNetworkCode: "AIRTEL_TZ", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Tanzania", Country: "TZ"},
"63510": {MobileNetworkCode: "63510", MomoNetworkCode: "MTN_RW", MomoNetworkName: "MTN Mobile Money", TelcoName: "MTN Rwanda", Country: "RW"},
"63513": {MobileNetworkCode: "63513", MomoNetworkCode: "TIGO_RW", MomoNetworkName: "Tigo Cash", TelcoName: "Tigo Rwanda", Country: "RW"},
"63514": {MobileNetworkCode: "63514", MomoNetworkCode: "AIRTEL_RW", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Rwanda", Country: "RW"},
"62120": {MobileNetworkCode: "62120", MomoNetworkCode: "AIRTEL_NG", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Nigeria", Country: "NG"},
"62130": {MobileNetworkCode: "62130", MomoNetworkCode: "MTN_NG", MomoNetworkName: "MTN MoMo", TelcoName: "MTN Nigeria", Country: "NG"},
"62150": {MobileNetworkCode: "62150", MomoNetworkCode: "GLO_NG", MomoNetworkName: "Glo Mobile Money", TelcoName: "Glo Nigeria", Country: "NG"},
"62160": {MobileNetworkCode: "62160", MomoNetworkCode: "ETISALAT_NG", MomoNetworkName: "9Mobile", TelcoName: "Etisalat Nigeria", Country: "NG"},
"62001": {MobileNetworkCode: "62001", MomoNetworkCode: "MTN_GH", MomoNetworkName: "MTN Mobile Money", TelcoName: "MTN Ghana", Country: "GH"},
"62002": {MobileNetworkCode: "62002", MomoNetworkCode: "VODAFONE_GH", MomoNetworkName: "Vodafone Cash", TelcoName: "Vodafone Ghana", Country: "GH"},
"62006": {MobileNetworkCode: "62006", MomoNetworkCode: "AIRTELTIGO_GH", MomoNetworkName: "AirtelTigo Money", TelcoName: "AirtelTigo Ghana", Country: "GH"},
"63601": {MobileNetworkCode: "63601", MomoNetworkCode: "ETHIO", MomoNetworkName: "Telebirr", TelcoName: "EthioTelecom", Country: "ET"},
"64501": {MobileNetworkCode: "64501", MomoNetworkCode: "AIRTEL_ZM", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Zambia", Country: "ZM"},
"64502": {MobileNetworkCode: "64502", MomoNetworkCode: "MTN_ZM", MomoNetworkName: "MTN Mobile Money", TelcoName: "MTN Zambia", Country: "ZM"},
"65001": {MobileNetworkCode: "65001", MomoNetworkCode: "TNM_MW", MomoNetworkName: "TNM Mpamba", TelcoName: "TNM Malawi", Country: "MW"},
"65010": {MobileNetworkCode: "65010", MomoNetworkCode: "AIRTEL_MW", MomoNetworkName: "Airtel Money", TelcoName: "Airtel Malawi", Country: "MW"},
"65501": {MobileNetworkCode: "65501", MomoNetworkCode: "VODACOM_ZA", MomoNetworkName: "Vodacom", TelcoName: "Vodacom South Africa", Country: "ZA"},
"65502": {MobileNetworkCode: "65502", MomoNetworkCode: "TELKOM_ZA", MomoNetworkName: "Telkom Pay", TelcoName: "Telkom South Africa", Country: "ZA"},
"65507": {MobileNetworkCode: "65507", MomoNetworkCode: "CELLC_ZA", MomoNetworkName: "CellC", TelcoName: "CellC South Africa", Country: "ZA"},
"65510": {MobileNetworkCode: "65510", MomoNetworkCode: "MTN_ZA", MomoNetworkName: "MTN Mobile Money", TelcoName: "MTN South Africa", Country: "ZA"},
"99999": {MobileNetworkCode: "99999", MomoNetworkCode: "SANDBOX", MomoNetworkName: "Sandbox Network", TelcoName: "Athena", Country: "KE"},
}
NetworkMappings maps telco's mobile network code (MCC + MNC) to mobile money networks
Functions ¶
func GetLocalizedMessage ¶
GetLocalizedMessage is a helper function for backward compatibility
Types ¶
type BioUpdate ¶ added in v1.0.0
type BioUpdate struct {
BirthDate string // YYYY-MM-DD
Address string
City string
PostalCode string
}
BioUpdate carries the optional SEP-9 fields set from the account menu.
type CarrierRepaymentPrompter ¶ added in v1.6.0
type CarrierRepaymentPrompter interface {
// PromptRepaymentVia pushes a prompt for the full payoff on the named
// cash-in provider. The provider id is the registry's own ("mpesa",
// "airtel"); an unregistered one is an error, never a silent fallback.
PromptRepaymentVia(ctx context.Context, loanID, phoneNumber, providerID string) error
}
CarrierRepaymentPrompter is the capability of pushing a prompt on a named rail. The borrower chooses their network from the repay menu rather than having it inferred from their MSISDN: prefix tables go stale silently as the regulator reallocates ranges, and a prompt pushed at the wrong network is a support call.
type CustomMenuPreset ¶
type CustomMenuPreset struct {
// contains filtered or unexported fields
}
CustomMenuPreset allows for the definition of arbitrary menu flows.
func NewCustomMenuPreset ¶
func NewCustomMenuPreset(name string) *CustomMenuPreset
NewCustomMenuPreset creates a new instance of CustomMenuPreset.
func (*CustomMenuPreset) AddMenu ¶
func (p *CustomMenuPreset) AddMenu(menu *Menu) *CustomMenuPreset
AddMenu adds a menu to the custom menu preset.
func (*CustomMenuPreset) GetName ¶
func (p *CustomMenuPreset) GetName() string
GetName returns the name of the custom menu preset.
func (*CustomMenuPreset) Initialize ¶
func (p *CustomMenuPreset) Initialize(registry *MenuRegistry)
NewCustomMenuPreset creates a new instance of CustomMenuPreset.
type HandlerDeps ¶ added in v1.1.2
type HandlerDeps struct {
SessionManager *SessionManager
MenuRegistry *MenuRegistry
UserService UserService
LoanService LoanService
RateService RateService
PINService PINService
AccountNotifier contracts.AccountNotifier
LoanNotifier contracts.LoanNotifier
// RepayPaybill is the mobile-money paybill number shown on the repay
// screens — the configured M-Pesa collection shortcode. Blank hides the
// mobile-money option entirely.
RepayPaybill string
// MpesaPrompter enables the STK prompt rail when the loan service
// satisfies RepaymentPrompter.
MpesaPrompter bool
// AirtelPrompter enables the Airtel Money prompt rail when the loan
// service satisfies CarrierRepaymentPrompter. Separate from
// MpesaPrompter because the rails are enabled by separate integrations
// and either can be live without the other.
AirtelPrompter bool
}
HandlerDeps are the collaborators and settings a USSDHandler needs. SessionManager and MenuRegistry are required; the services may be nil, in which case the flows that need them degrade to an error screen.
type InMemoryLocalizer ¶
type InMemoryLocalizer struct {
// contains filtered or unexported fields
}
InMemoryLocalizer implements Localizer using an in-memory map for fast translation lookups.
func NewInMemoryLocalizer ¶
func NewInMemoryLocalizer(defaultLang string) *InMemoryLocalizer
NewInMemoryLocalizer creates a new in-memory localizer
func (*InMemoryLocalizer) AddTranslation ¶
func (l *InMemoryLocalizer) AddTranslation(key, language, message string)
AddTranslation adds a translation
func (*InMemoryLocalizer) Get ¶
func (l *InMemoryLocalizer) Get(language, key string) string
Get retrieves a localized message
func (*InMemoryLocalizer) GetWithDefault ¶
func (l *InMemoryLocalizer) GetWithDefault(language, key, defaultMsg string) string
GetWithDefault retrieves a localized message with a default fallback
func (*InMemoryLocalizer) HasKey ¶
func (l *InMemoryLocalizer) HasKey(key string) bool
HasKey checks if a key exists
func (*InMemoryLocalizer) LoadStandardTranslations ¶
func (l *InMemoryLocalizer) LoadStandardTranslations()
LoadStandardTranslations loads standard translations
type LoanApproval ¶
LoanApproval contains the result of a loan eligibility check, including approval status and terms.
type LoanProductConfig ¶
type LoanProductConfig struct {
ProductID string // UUID of the loan product record.
MinAmountCents int64 // Minimum loan in fiat cents (e.g. 50000 = KES 500).
MaxAmountCents int64 // Maximum auto-approved loan in fiat cents (e.g. 300000 = KES 3,000).
Currency string // ISO 4217 currency code (e.g. "KES").
DurationDays int // Fixed loan term in days (e.g. 30).
RepaymentSchedule string // Repayment cadence (e.g. "lump_sum").
InterestRateBps int32 // Fallback annual rate in basis points; vault APR takes precedence.
OriginationFeeBps int32 // One-off fee in bps (1 bps = 0.01 %). Zero when product has none.
}
LoanProductConfig provides loan product parameters for the USSD flow. It is loaded once at startup from the highest-priority active loan product.
type LoanRequest ¶
type LoanRequest struct {
UserID string
AccountID string // Database UUID of the user's account record.
StellarAddress string // Stellar public key for vault borrow recipient.
ProductID string // Loan product ID from LoanProductConfig.
PhoneNumber string
RecipientName string
NationalID string
CountryCode string
NetworkCode string
NetworkName string
PrincipalAmount int64 // USDC amount in stroops (converted from local currency).
PrincipalAsset string
DurationDays int
RepaymentSched string
LocalAmount int64 // Local currency amount in cents (e.g. KES cents).
LocalCurrency string // ISO 4217 currency code (e.g. "KES").
ConversionRate float64 // YellowCard buy rate at quote time (e.g. 153.50).
// Off-ramp routing — selects the provider (mobile money vs cash pickup).
// Empty defaults to mobile money for back-compat with menus that don't
// expose the cash-pickup branch yet.
PayoutMethod string
// Cash-pickup-only KYC fields (MoneyGram). Ignored by mobile-money flows.
FirstName string
LastName string
BirthDate string // ISO-8601 (YYYY-MM-DD)
Address string
PostalCode string
City string
AddressCountryCode string
ChildAccountIndex uint32 // per-user Stellar derivation index for SEP-10 memo
}
LoanRequest represents a loan request from the USSD flow.
type LoanService ¶
type LoanService interface {
// GetUserLoans returns all loans for the given user, formatted for USSD display.
GetUserLoans(ctx context.Context, userID string) ([]any, error)
// RequestLoan orchestrates the full loan disbursement cycle.
RequestLoan(ctx context.Context, req *LoanRequest) (any, error)
// CheckLoanEligibility checks whether the user qualifies for the requested amount.
CheckLoanEligibility(ctx context.Context, userID string, amount int64, duration int) (*LoanApproval, error)
// GetProductConfig returns the active loan product parameters used by the
// USSD flow (limits, duration, schedule). Returns nil when no product is configured.
GetProductConfig() *LoanProductConfig
// GetRepaymentQuote returns the amount currently owed on a loan, in both
// USDC stroops and local currency cents. The USDC figure is derived from
// the vault's current borrow_index relative to the index at origination;
// the local figure applies the latest FX. Returns an error (hard-fail —
// no stale fallback) when the vault or FX is unavailable.
GetRepaymentQuote(ctx context.Context, loanID string) (*RepaymentQuote, error)
// InitiateRepayment opens a MoneyGram cash deposit for the loan.
//
// It returns as soon as the request is accepted, not when the deposit
// exists. Quoting, SEP-10 authentication, SEP-24 initiation, link
// shortening and the SMS together took over fifteen seconds against the
// sandbox — past the point Africa's Talking abandons a USSD session, so
// waiting for them leaves the borrower's screen dead before it renders.
//
// Everything the borrower needs arrives by SMS: the interactive link on
// success, a failure notice otherwise. Nothing on the USSD screen depends
// on the outcome, which is what makes returning early honest rather than a
// shortcut. An error here means the request was refused outright.
InitiateRepayment(ctx context.Context, loanID, phoneNumber string) error
}
LoanService defines the interface for loan-related operations. Implementations live in the lending module; the USSD handler depends only on this consumer-defined interface.
type Localizer ¶
type Localizer interface {
// Get retrieves a localized message.
Get(language, key string) string
// GetWithDefault retrieves a localized message with a default fallback.
GetWithDefault(language, key, defaultMsg string) string
// HasKey checks if a key exists.
HasKey(key string) bool
// AddTranslation adds a translation.
AddTranslation(key, language, message string)
}
Localizer defines the contract for retrieving translated strings based on language preferences.
type Menu ¶
type Menu struct {
ID string
Title map[string]string // Language to Title
Options []MenuOption
ParentMenu string
}
Menu is a single screen: a localized title and the options rendered beneath it. The registry is a rendering store, not a router — USSDHandler routes on the session's current menu with an explicit switch.
type MenuBuilder ¶
type MenuBuilder struct {
// contains filtered or unexported fields
}
MenuBuilder provides a fluent interface for programmatically constructing Menu instances.
func NewMenuBuilder ¶
func NewMenuBuilder(id string) *MenuBuilder
NewMenuBuilder creates a new menu builder
func (*MenuBuilder) WithOption ¶
func (mb *MenuBuilder) WithOption(key string, labels map[string]string, targetMenu string) *MenuBuilder
WithOption adds an option to the menu
func (*MenuBuilder) WithParentMenu ¶
func (mb *MenuBuilder) WithParentMenu(parentMenu string) *MenuBuilder
WithParentMenu sets the parent menu
func (*MenuBuilder) WithTitle ¶
func (mb *MenuBuilder) WithTitle(language, title string) *MenuBuilder
WithTitle sets the menu title for a language
type MenuOption ¶
type MenuOption struct {
Key string
Label map[string]string // Language to Label
TargetMenu string
}
MenuOption represents a user-selectable choice within a Menu.
type MenuPreset ¶
type MenuPreset interface {
// Initialize loads menus into the registry.
Initialize(registry *MenuRegistry)
// GetName returns the preset name.
GetName() string
}
MenuPreset defines a reusable collection of menus for a specific workflow or use case.
type MenuRegistry ¶
type MenuRegistry struct {
// contains filtered or unexported fields
}
MenuRegistry stores and provides access to all configured menus in the USSD application.
func NewMenuRegistry ¶
func NewMenuRegistry() *MenuRegistry
NewMenuRegistry creates a new menu registry
func (*MenuRegistry) Clear ¶
func (mr *MenuRegistry) Clear()
Clear removes all menus from the registry
func (*MenuRegistry) Get ¶
func (mr *MenuRegistry) Get(menuID string) (*Menu, error)
Get retrieves a menu by ID
func (*MenuRegistry) GetAll ¶
func (mr *MenuRegistry) GetAll() map[string]*Menu
GetAll returns all registered menus
func (*MenuRegistry) Register ¶
func (mr *MenuRegistry) Register(menu *Menu)
Register registers a menu
func (*MenuRegistry) Unregister ¶
func (mr *MenuRegistry) Unregister(menuID string)
Unregister removes a menu from the registry
type NetworkMapper ¶
type NetworkMapper struct{}
NetworkMapper maps telcos mobile network codes (MCC + MNC) to MoMo network info
func NewNetworkMapper ¶
func NewNetworkMapper() *NetworkMapper
NewNetworkMapper creates a new network mapper
func (*NetworkMapper) GetCountryFromMobileNetworkCode ¶
func (m *NetworkMapper) GetCountryFromMobileNetworkCode(MobileNetworkCode string) string
GetCountryFromMobileNetworkCode returns the country code for a mobile network code
func (*NetworkMapper) MapMobileNetworkCode ¶
func (m *NetworkMapper) MapMobileNetworkCode(MobileNetworkCode string) (*NetworkMapping, error)
MapMobileNetworkCode maps a telco's mobile network code (MCC + MNC) to MoMo network info
type NetworkMapping ¶
type NetworkMapping struct {
MobileNetworkCode string // Telco's mobile network code (MCC + MNC) (e.g., "63902")
MomoNetworkCode string // Momo network code (e.g., "MPESA")
MomoNetworkName string // Momo Display name (e.g., "M-Pesa")
TelcoName string // Telco name (e.g., "Safaricom Kenya")
Country string // ISO country code (e.g., "KE")
}
NetworkMapping maps telco's mobile network code (MCC + MNC) to MoMo network info
type PINService ¶
type PINService interface {
// SetPIN creates a new PIN for a user (during registration).
SetPIN(ctx context.Context, userID, pin string) error
// VerifyPIN checks the supplied PIN against the stored hash.
// Returns (true, nil) on success, (false, nil) on wrong PIN.
VerifyPIN(ctx context.Context, userID, pin string) (bool, error)
// ChangePIN verifies the old PIN and sets a new one.
ChangePIN(ctx context.Context, userID, oldPin, newPin string) error
// ResetPIN sets a new PIN after identity verification (security questions).
ResetPIN(ctx context.Context, userID, newPin string) error
// IsLocked reports whether the account is locked and when the lockout expires.
IsLocked(ctx context.Context, userID string) (bool, time.Time, error)
// HasPIN reports whether the user has a PIN set.
HasPIN(ctx context.Context, userID string) (bool, error)
// SetSecurityQuestions stores hashed answers for the given questions.
SetSecurityQuestions(ctx context.Context, userID string, questions []pin.QuestionAnswer) error
// VerifySecurityAnswers checks the supplied answers against stored hashes.
VerifySecurityAnswers(ctx context.Context, userID string, answers []pin.QuestionAnswer) (bool, error)
// GetUserQuestionIDs returns the predefined question IDs the user has configured.
GetUserQuestionIDs(ctx context.Context, userID string) ([]int, error)
// GetRemainingAttempts returns the number of PIN attempts left before lockout.
GetRemainingAttempts(ctx context.Context, userID string) (int, error)
}
PINService defines the interface for PIN management operations used by the USSD handler. It is satisfied by pin.Service.
type RateService ¶
type RateService interface {
// GetExchangeRate returns the current sell rate for the specified currency.
GetExchangeRate(ctx context.Context, currency string) (sellRate float64, err error)
}
RateService provides exchange rate lookups for local currency conversion.
type RegisterUserRequest ¶
type RegisterUserRequest struct {
MobileNumber string
MobileCountryCode string
NetworkCode string
FullName string
NationalID string
PreferredLanguage string
// PinHash is the pre-hashed PIN (from pin.HashPIN), written atomically with
// the user so no PIN-less account is ever persisted. Empty only when the
// deployment has no PIN service configured.
PinHash string
PinSetAt *time.Time
// Optional SEP-9 bio (MoneyGram cash-pickup prefill). Empty when the user
// skips the bio step. BirthDate is YYYY-MM-DD.
BirthDate string
Address string
City string
PostalCode string
}
RegisterUserRequest contains the necessary information to register a new user via USSD.
type RepaymentPrompter ¶ added in v1.4.1
type RepaymentPrompter interface {
// PromptRepayment pushes a prompt at the borrower's handset for the full
// payoff. It returns once the prompt is accepted for delivery, not when
// it is paid; an error means the push was refused outright.
PromptRepayment(ctx context.Context, loanID, phoneNumber string) error
}
RepaymentPrompter is the capability of pushing a payment prompt at the borrower's handset — M-Pesa Express today. A LoanService may satisfy it; the repay rail offers the prompt only when the wired service does and the builder enables it.
type RepaymentQuote ¶ added in v1.0.0
type RepaymentQuote struct {
LoanID string
AmountUSDCStroops int64 // What the borrower owes in USDC, stroops.
AmountLocalCents int64 // Same, converted at the FX rate below.
LocalCurrency string // ISO 4217 (e.g. "KES").
BorrowIndexAtQuote int64 // WAD scale (1e18).
FXRate float64 // local-per-USD at quote time.
QuoteSource string // "mg_primary" | "yc_fallback" | "stale_cache".
AsOf time.Time
}
RepaymentQuote is the live amount owed on a loan, computed from the vault borrow_index + current FX. Stored repayment quotes are advisory only — this is the figure to show on USSD screens, in SMS reminders, and to validate borrower payments against (with ±2 % tolerance).
type Session ¶
type Session struct {
SessionID string `json:"session_id"`
PhoneNumber string `json:"phone_number"`
ServiceCode string `json:"service_code"`
NetworkCode string `json:"network_code"`
UserID string `json:"user_id,omitempty"`
CurrentMenu string `json:"current_menu"`
PreviousMenus []string `json:"previous_menus"`
Language string `json:"language"`
Data map[string]any `json:"data"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
Session represents a single interactive USSD session with a mobile user.
type SessionManager ¶
type SessionManager struct {
// contains filtered or unexported fields
}
SessionManager handles the storage, retrieval, and lifecycle of USSD sessions.
func NewSessionManager ¶
func NewSessionManager(cache *redis.Client, sessionDuration time.Duration) *SessionManager
func (*SessionManager) CreateSession ¶
func (sm *SessionManager) CreateSession(ctx context.Context, sessionID, phoneNumber, serviceCode, networkCode string) (*Session, error)
CreateSession creates a new USSD session
func (*SessionManager) DeleteSession ¶
func (sm *SessionManager) DeleteSession(ctx context.Context, sessionID string) error
DeleteSession deletes a session
func (*SessionManager) ExtendSession ¶
func (sm *SessionManager) ExtendSession(ctx context.Context, sessionID string) error
ExtendSession extends the session expiration
func (*SessionManager) GetOrCreateSession ¶
func (sm *SessionManager) GetOrCreateSession(ctx context.Context, sessionID, phoneNumber, serviceCode, networkCode string) (*Session, error)
GetOrCreateSession gets an existing session or creates a new one
func (*SessionManager) GetSession ¶
GetSession retrieves a session by session ID
func (*SessionManager) GoBack ¶
func (sm *SessionManager) GoBack(ctx context.Context, sessionID string) error
GoBack navigates to previous menu
func (*SessionManager) NavigateToMenu ¶
func (sm *SessionManager) NavigateToMenu(ctx context.Context, sessionID, menu string) error
NavigateToMenu navigates to a new menu
func (*SessionManager) SaveSession ¶
func (sm *SessionManager) SaveSession(ctx context.Context, session *Session) error
SaveSession saves a session to cache
func (*SessionManager) SetLanguage ¶
func (sm *SessionManager) SetLanguage(ctx context.Context, sessionID, language string) error
SetLanguage sets the session language
func (*SessionManager) SetUserID ¶
func (sm *SessionManager) SetUserID(ctx context.Context, sessionID, userID string) error
SetUserID associates a user ID with the session
func (*SessionManager) UpdateSession ¶
func (sm *SessionManager) UpdateSession(ctx context.Context, sessionID string, updates func(*Session)) error
UpdateSession updates session data
type SimplifiedMenuPreset ¶
type SimplifiedMenuPreset struct{}
SimplifiedMenuPreset provides a streamlined set of menus with fewer steps.
func NewSimplifiedMenuPreset ¶
func NewSimplifiedMenuPreset() *SimplifiedMenuPreset
NewSimplifiedMenuPreset creates a new instance of SimplifiedMenuPreset.
func (*SimplifiedMenuPreset) GetName ¶
func (p *SimplifiedMenuPreset) GetName() string
GetName returns the name of the simplified menu preset.
func (*SimplifiedMenuPreset) Initialize ¶
func (p *SimplifiedMenuPreset) Initialize(registry *MenuRegistry)
Initialize registers the simplified menu preset.
type StandardLoanMenuPreset ¶
type StandardLoanMenuPreset struct{}
StandardLoanMenuPreset provides the standard set of menus for the default loan application flow.
func NewStandardLoanMenuPreset ¶
func NewStandardLoanMenuPreset() *StandardLoanMenuPreset
NewStandardLoanMenuPreset creates a new instance of StandardLoanMenuPreset.
func (*StandardLoanMenuPreset) GetName ¶
func (p *StandardLoanMenuPreset) GetName() string
GetName returns the name of the menu preset.
func (*StandardLoanMenuPreset) Initialize ¶
func (p *StandardLoanMenuPreset) Initialize(registry *MenuRegistry)
Initialize registers all menus for the standard loan flow including the restructured main menu (4 items), PIN management menus, and security question setup menus.
type TranslationBuilder ¶
type TranslationBuilder struct {
// contains filtered or unexported fields
}
TranslationBuilder provides a fluent interface for populating an InMemoryLocalizer.
func NewTranslationBuilder ¶
func NewTranslationBuilder(localizer *InMemoryLocalizer) *TranslationBuilder
NewTranslationBuilder creates a new translation builder
func (*TranslationBuilder) Add ¶
func (tb *TranslationBuilder) Add(key string, translations map[string]string) *TranslationBuilder
Add adds a translation with multiple languages
func (*TranslationBuilder) Build ¶
func (tb *TranslationBuilder) Build() *InMemoryLocalizer
Build returns the localizer
type USSDHandler ¶
type USSDHandler struct {
// contains filtered or unexported fields
}
USSDHandler orchestrates the processing of incoming USSD requests, managing sessions and routing to appropriate menus.
func NewUSSDHandler ¶
func NewUSSDHandler(deps HandlerDeps) *USSDHandler
NewUSSDHandler builds the handler.
func (*USSDHandler) HandleRequest ¶
func (h *USSDHandler) HandleRequest(ctx context.Context, sessionID, phoneNumber, serviceCode, networkCode, input string) (string, error)
HandleRequest handles a USSD request
type USSDProvider ¶
type USSDProvider interface {
// ParseRequest parses the provider-specific HTTP request into a standardized USSDRequest.
ParseRequest(ctx context.Context, data map[string]string) (*USSDRequest, error)
// FormatResponse formats the USSDResponse into the provider-specific response format.
FormatResponse(ctx context.Context, response *USSDResponse) (any, error)
// GetProviderName returns the name of the USSD provider.
GetProviderName() string
// ValidateRequest validates the incoming request from the provider.
ValidateRequest(ctx context.Context, data map[string]string) error
}
USSDProvider defines the contract for integrating with different telecommunication USSD gateways.
type USSDRequest ¶
type USSDRequest struct {
SessionID string
PhoneNumber string
Input string
ServiceCode string
NetworkCode string
ProviderData map[string]string // For provider-specific data
}
USSDRequest encapsulates the normalized data received from a USSD gateway provider.
type USSDResponse ¶
USSDResponse contains the formatted data to be returned to the USSD gateway provider.
type USSDService ¶
type USSDService struct {
// contains filtered or unexported fields
}
USSDService manages multiple USSD providers and routes incoming requests to the appropriate handler.
func NewUSSDService ¶
func NewUSSDService(handler *USSDHandler) *USSDService
NewUSSDService creates a new USSD service.
func (*USSDService) DeleteAllProviders ¶
func (s *USSDService) DeleteAllProviders() error
DeleteAllProviders removes all USSD providers from the service.
func (*USSDService) DeleteProvider ¶
func (s *USSDService) DeleteProvider(name string) error
DeleteProvider removes a USSD provider from the service.
func (*USSDService) GetProvider ¶
func (s *USSDService) GetProvider(name string) (USSDProvider, error)
GetProvider retrieves a USSD provider by name.
func (*USSDService) GetProviders ¶
func (s *USSDService) GetProviders() map[string]USSDProvider
GetProviders retrieves all USSD providers.
func (*USSDService) HandleRequest ¶
func (s *USSDService) HandleRequest(ctx context.Context, providerName string, data map[string]string) (any, error)
HandleRequest processes a USSD request using the specified provider.
func (*USSDService) RegisterProvider ¶
func (s *USSDService) RegisterProvider(name string, provider USSDProvider)
RegisterProvider registers a USSD provider with the service.
type UserService ¶
type UserService interface {
// GetUserWithAccounts retrieves a user and their associated accounts.
GetUserWithAccounts(ctx context.Context, userIDOrPhone string) (any, []any, error)
// RegisterUser creates a new user account based on the provided registration request.
RegisterUser(ctx context.Context, req *RegisterUserRequest) (any, []any, error)
// NationalIDExists reports whether a user is already registered with the
// given national ID, so registration can reject a duplicate up front rather
// than at the final atomic insert (the DB unique constraint is the real
// guard; this is the fast, friendly path).
NationalIDExists(ctx context.Context, nationalID string) (bool, error)
// UpdateBio sets the optional SEP-9 bio fields on an existing user (added
// post-registration via the account menu for faster cash pickup). Empty
// fields are left unchanged; birthDate is YYYY-MM-DD.
UpdateBio(ctx context.Context, userID string, bio BioUpdate) error
// GetUserIDByNationalID resolves the account behind a national ID. Used by
// new-SIM recovery to find the existing account when registration hits the
// duplicate-ID case. Returns "" when no user holds that ID.
GetUserIDByNationalID(ctx context.Context, nationalID string) (string, error)
// RebindMobileNumber moves an account to the dialing SIM. Only called after
// the caller has verified ownership via security questions — it changes the
// account's identity anchor.
RebindMobileNumber(ctx context.Context, userID, mobileNumber string) error
}
UserService defines the contract for user identity and account management operations required by the USSD flow.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package adapters holds the USSD-to-domain glue: each file implements a narrow port interface declared elsewhere against the concrete user, account, off-ramp, and Stellar services.
|
Package adapters holds the USSD-to-domain glue: each file implements a narrow port interface declared elsewhere against the concrete user, account, off-ramp, and Stellar services. |
|
providers
|
|
|
africastalking
Package africastalking implements the USSD provider adapter for AfricasTalking.
|
Package africastalking implements the USSD provider adapter for AfricasTalking. |