notifications

package
v1.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 29, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package notifications delivers user-facing messages — loan lifecycle and account/PIN events — and is the concrete implementation of the notifier ports declared in the contracts package. It separates three concerns: what to say, how it reads, and how it is delivered.

Transport

Notifier is a thin send-only interface — Send a message to a recipient — that decouples the rest of the package from any particular channel. SMSNotifier implements it over an SMS provider; NoOpNotifier discards everything, for tests and for environments where notifications are switched off. Phone numbers are redacted in logs via the shared pkg/phone helper.

Composition

SMSLoanNotifier and SMSAccountNotifier satisfy contracts.LoanNotifier and contracts.AccountNotifier. Each takes a Notifier plus a set of templates, picks the template for the event it was asked to send, and hands the result to the transport. Callers depend on the contracts interface, so the transport and wording can change without touching them.

Templates

LoanTemplates and AccountTemplates hold one renderer per event — a func from the notification to the message text, not a format string. The compiler therefore checks both the fields a message reads and the verbs it formats them with, which a positional Sprintf template cannot do.

The copy shipped here is deliberately brand-free: it names no company, no support URL and no USSD code, because those belong to whoever builds on the platform and, in the case of the USSD code, vary per deployment. Builders pass WithLoanTemplates or WithAccountTemplates to override the fields they care about; fields left nil keep the default, so overriding one message does not mean rewriting all of them. Values that vary by environment reach the copy by closure capture at construction rather than through a config type threaded into this package.

Validation

The constructors return an error. Before returning a notifier they render every merged template against SentinelLoanNotification or SentinelAccountNotification and reject any that is unset, renders empty, or contains a rune outside GSM 03.38 — one such rune forces the whole SMS to UCS-2 and cuts a segment from 160 characters to 70. Broken copy therefore fails at startup rather than on a recipient's handset. GSM7Len and Segments are exported so builders can make the same checks over their own templates.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DaysUntilDue added in v1.1.0

func DaysUntilDue(n contracts.LoanNotification) int

DaysUntilDue returns whole days between now and the notification's due date, negative once overdue and zero when no due date is set.

func DueDateText added in v1.1.1

func DueDateText(n contracts.LoanNotification) string

DueDateText renders a notification's due date as YYYY-MM-DD, or "-" when the loan has none. Templates must not format DueDate directly: it is a pointer and is nil until a loan is disbursed.

func ExpiresInText added in v1.1.2

func ExpiresInText(n contracts.LoanNotification) string

ExpiresInText renders how long remains on an opened cash deposit, in whole hours or days.

func GSM7Len added in v1.1.0

func GSM7Len(s string) (septets int, bad rune, ok bool)

GSM7Len returns the septet count of s. When s contains a rune outside GSM 03.38 it returns ok=false along with the offending rune, and the count is meaningless.

func Segments added in v1.1.0

func Segments(septets int) int

Segments returns how many concatenated SMS parts a GSM-7 message of the given septet count occupies. Concatenation headers cost seven septets per part, so anything past a single segment carries 153 rather than 160.

func SentinelAccountNotification added in v1.1.0

func SentinelAccountNotification() contracts.AccountNotification

SentinelAccountNotification returns a fully-populated account notification for the same purpose as SentinelLoanNotification.

func SentinelLoanNotification added in v1.1.0

func SentinelLoanNotification() contracts.LoanNotification

SentinelLoanNotification returns a fully-populated loan notification used to exercise templates at construction time. Every field carries a worst-case realistic value so validation measures the longest message a template can produce, not the shortest.

Types

type AccountMessage added in v1.1.0

type AccountMessage func(n contracts.AccountNotification) string

AccountMessage renders one account or PIN lifecycle notification.

type AccountOption added in v1.1.0

type AccountOption func(*accountConfig)

AccountOption configures an SMSAccountNotifier at construction.

func WithAccountLanguageResolver added in v1.1.0

func WithAccountLanguageResolver(r LanguageResolver) AccountOption

WithAccountLanguageResolver supplies the resolver consulted when a notification leaves Language empty.

func WithAccountTemplateSet added in v1.1.0

func WithAccountTemplateSet(set map[string]*AccountTemplates) AccountOption

WithAccountTemplateSet applies WithAccountTemplates for every language in the map.

func WithAccountTemplates added in v1.1.0

func WithAccountTemplates(lang string, t *AccountTemplates) AccountOption

WithAccountTemplates overrides account and PIN copy for one language, with the same per-field fallback as WithLoanTemplates.

type AccountTemplates

type AccountTemplates struct {
	// RegistrationSuccess welcomes a user after successful registration.
	RegistrationSuccess AccountMessage
	// RegistrationFailed reports that registration could not be completed.
	RegistrationFailed AccountMessage
	// WrongAttempt alerts on an incorrect PIN entry and counts down to lockout.
	WrongAttempt AccountMessage
	// AccountLocked is the security alert sent once repeated failures lock the
	// account.
	AccountLocked AccountMessage
	// PINChanged confirms a successful PIN change.
	PINChanged AccountMessage
	// PINChangeFailed alerts on an unsuccessful PIN change.
	PINChangeFailed AccountMessage
	// PINReset confirms a successful reset through the recovery flow.
	PINReset AccountMessage
	// PINResetFailed alerts on an unsuccessful reset.
	PINResetFailed AccountMessage
}

AccountTemplates holds one message renderer per account and PIN lifecycle event.

func DefaultAccountTemplates

func DefaultAccountTemplates() *AccountTemplates

DefaultAccountTemplates returns brand-free English account and PIN copy.

type LanguageResolver added in v1.0.0

type LanguageResolver func(ctx context.Context, phoneNumber string) string

LanguageResolver returns the preferred SMS language (ISO code en/sw/fr) for a recipient phone number. Used by notifiers when a notification doesn't pin its own Language.

type LoanMessage added in v1.1.0

type LoanMessage func(n contracts.LoanNotification) string

LoanMessage renders one loan lifecycle notification. Taking the whole notification rather than positional arguments lets the compiler check both the fields a message reads and the verbs it formats them with.

type LoanOption added in v1.1.0

type LoanOption func(*loanConfig)

LoanOption configures an SMSLoanNotifier at construction.

func WithLoanLanguageResolver added in v1.1.0

func WithLoanLanguageResolver(r LanguageResolver) LoanOption

WithLoanLanguageResolver supplies the resolver consulted when a notification leaves Language empty. Without one, such notifications render in English.

func WithLoanTemplateSet added in v1.1.0

func WithLoanTemplateSet(set map[string]*LoanTemplates) LoanOption

WithLoanTemplateSet applies WithLoanTemplates for every language in the map, which is how a builder that keeps its copy in one place hands the whole set over at once.

func WithLoanTemplates added in v1.1.0

func WithLoanTemplates(lang string, t *LoanTemplates) LoanOption

WithLoanTemplates overrides loan copy for one language. Fields left nil keep the platform default, so a builder writes only the messages it wants to change. Calling it more than once for the same language replaces the earlier override rather than merging the two.

type LoanTemplates

type LoanTemplates struct {
	Approved LoanMessage
	Rejected LoanMessage
	// Disbursed announces that fiat has been pushed to the borrower.
	Disbursed LoanMessage
	// Failed is the credit-default notice.
	Failed LoanMessage
	// OffRampFailed is sent when vault borrow succeeded but the off-ramp could
	// not be initiated or completed and the USDC has been returned to the
	// vault. The borrower owes nothing — distinct from the credit-default
	// Failed.
	OffRampFailed LoanMessage
	// CashPickupApproved replaces Approved for cash-pickup loans, whose
	// generic wording implies a push disbursement. A second SMS with the
	// MoneyGram interactive URL follows once the off-ramp is initiated (see
	// CashPickupInitiated).
	CashPickupApproved LoanMessage
	// Repaid confirms the treasury-to-vault leg confirmed and the loan is
	// closed — the terminal notification, sent once, distinct from
	// RepaymentReceived which fires earlier (cash on the treasury, vault leg
	// not yet confirmed).
	Repaid            LoanMessage
	RepaymentReceived LoanMessage
	// RepaymentOverdue, RepaymentSoon and RepaymentUpcoming are selected by
	// [SMSLoanNotifier.NotifyRepaymentReminder] from the days remaining; use
	// [DaysUntilDue] to render that count.
	RepaymentOverdue  LoanMessage
	RepaymentSoon     LoanMessage
	RepaymentUpcoming LoanMessage
	// RepaymentInitiated carries the MoneyGram interactive URL for a
	// borrower-initiated cash repayment. The USSD session ends before the
	// borrower can act on it, so this SMS is the only delivery. The copy must
	// make clear the borrower is paying, not collecting — the cash-pickup SMS
	// carries a superficially similar link with the opposite meaning.
	RepaymentInitiated LoanMessage
	// RepaymentFailed reports that a deposit could not be opened. Nothing was
	// paid and the loan is untouched, so the copy must not read as a default
	// notice — it asks the borrower to try again.
	RepaymentFailed LoanMessage
	// RepaymentReference carries the code the borrower quotes at the agent
	// counter to pay in. Read from CashPickupRef.
	RepaymentReference LoanMessage
	// RepaymentMoreInfo carries MoneyGram's transaction page, sent when no
	// reference has been issued. Read from InteractiveURL.
	RepaymentMoreInfo LoanMessage
	// RepaymentPaybill carries the M-Pesa paybill instructions for a
	// mobile-money repayment. Read from PaybillNumber, LoanReference, and
	// DisplayAmount/DisplayCurrency.
	RepaymentPaybill LoanMessage
	// RepaymentWindowExpiring warns that an opened deposit is about to lapse.
	// Use [ExpiresInText] for the remaining time.
	RepaymentWindowExpiring LoanMessage
	// RepaymentExpired reports that an opened deposit lapsed unused. Nothing
	// was paid and the loan is untouched, so the copy must not read as a
	// default notice.
	RepaymentExpired LoanMessage
	// CashPickupInitiated carries the MoneyGram interactive URL. The URL is
	// single-use and the payout amount is not yet locked, so the copy must say
	// the final amount appears in the link.
	CashPickupInitiated LoanMessage
	// CashPickupReady carries the reference the borrower quotes at the agent.
	// Runs to two SMS segments; the support link is worth the second. Unlike
	// InteractiveURL, CashPickupInfoURL stays valid after settlement.
	CashPickupReady LoanMessage
	// Statement answers a user-initiated "My Loans" request with one loan's
	// reference, amount and due date. Use [DueDateText] for the date — a loan
	// may have none.
	Statement LoanMessage
	// CashPickupCancelled is sent when MoneyGram refunds a cash-pickup loan,
	// which usually means the borrower cancelled in MoneyGram's own app —
	// sometimes by mistake. The wording has to reassure rather than alarm:
	// nothing is owed and they can simply request again.
	CashPickupCancelled LoanMessage
}

LoanTemplates holds one message renderer per loan lifecycle event.

func DefaultLoanTemplates

func DefaultLoanTemplates() *LoanTemplates

DefaultLoanTemplates returns brand-free English loan copy.

type NoOpAccountNotifier

type NoOpAccountNotifier struct{}

NoOpAccountNotifier silently discards all account notifications. It is useful for testing and environments where SMS delivery is not configured.

func (*NoOpAccountNotifier) NotifyAccountLocked

func (*NoOpAccountNotifier) NotifyPINChangeFailed

func (*NoOpAccountNotifier) NotifyPINChanged

func (*NoOpAccountNotifier) NotifyPINReset

func (*NoOpAccountNotifier) NotifyPINResetFailed

func (*NoOpAccountNotifier) NotifyPINWrongAttempt

func (*NoOpAccountNotifier) NotifyRegistrationFailed

func (*NoOpAccountNotifier) NotifyRegistrationSuccess

type NoOpLoanNotifier

type NoOpLoanNotifier struct{}

NoOpLoanNotifier discards all loan notifications silently.

func (*NoOpLoanNotifier) NotifyLoanApproved

func (*NoOpLoanNotifier) NotifyLoanCashPickupApproved added in v1.0.0

func (*NoOpLoanNotifier) NotifyLoanCashPickupApproved(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyLoanCashPickupCancelled added in v1.0.0

func (*NoOpLoanNotifier) NotifyLoanCashPickupCancelled(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyLoanCashPickupInitiated added in v1.0.0

func (*NoOpLoanNotifier) NotifyLoanCashPickupInitiated(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyLoanCashPickupReady added in v1.0.0

func (*NoOpLoanNotifier) NotifyLoanCashPickupReady(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyLoanDisbursed

func (*NoOpLoanNotifier) NotifyLoanFailed

func (*NoOpLoanNotifier) NotifyLoanOffRampFailed added in v1.0.0

func (*NoOpLoanNotifier) NotifyLoanOffRampFailed(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyLoanRejected

func (*NoOpLoanNotifier) NotifyLoanRepaid added in v1.5.0

func (*NoOpLoanNotifier) NotifyLoanStatement added in v1.1.1

func (*NoOpLoanNotifier) NotifyRepaymentExpired added in v1.1.2

func (*NoOpLoanNotifier) NotifyRepaymentExpired(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentFailed added in v1.1.2

func (*NoOpLoanNotifier) NotifyRepaymentInitiated added in v1.1.2

func (*NoOpLoanNotifier) NotifyRepaymentInitiated(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentMoreInfo added in v1.1.2

func (*NoOpLoanNotifier) NotifyRepaymentMoreInfo(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentPaybill added in v1.4.1

func (*NoOpLoanNotifier) NotifyRepaymentPaybill(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentReceived

func (*NoOpLoanNotifier) NotifyRepaymentReceived(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentReference added in v1.1.2

func (*NoOpLoanNotifier) NotifyRepaymentReference(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentReminder

func (*NoOpLoanNotifier) NotifyRepaymentReminder(context.Context, contracts.LoanNotification) error

func (*NoOpLoanNotifier) NotifyRepaymentWindowExpiring added in v1.1.2

func (*NoOpLoanNotifier) NotifyRepaymentWindowExpiring(context.Context, contracts.LoanNotification) error

type NoOpNotifier

type NoOpNotifier struct{}

NoOpNotifier silently discards all messages. Useful for testing and environments where notifications are not configured.

func (*NoOpNotifier) Send

Send is a no-op.

type Notifier

type Notifier interface {
	Send(ctx context.Context, to string, message string) error
}

Notifier is a thin send-only interface that decouples notification logic from the underlying transport (SMS, push, email, etc.).

type SMSAccountNotifier

type SMSAccountNotifier struct {
	// contains filtered or unexported fields
}

SMSAccountNotifier implements contracts.AccountNotifier by rendering messages from AccountTemplates and delivering them through a Notifier transport (typically SMS).

func NewSMSAccountNotifier

func NewSMSAccountNotifier(notifier Notifier, opts ...AccountOption) (*SMSAccountNotifier, error)

NewSMSAccountNotifier creates a new SMSAccountNotifier over the built-in localized templates (en/sw/fr), adjusted by any options. Templates are validated against SentinelAccountNotification before it returns.

func (*SMSAccountNotifier) NotifyAccountLocked

func (s *SMSAccountNotifier) NotifyAccountLocked(ctx context.Context, n contracts.AccountNotification) error

NotifyAccountLocked sends a security alert when the account is locked.

func (*SMSAccountNotifier) NotifyPINChangeFailed

func (s *SMSAccountNotifier) NotifyPINChangeFailed(ctx context.Context, n contracts.AccountNotification) error

NotifyPINChangeFailed sends an alert when a PIN change attempt is unsuccessful.

func (*SMSAccountNotifier) NotifyPINChanged

NotifyPINChanged sends a confirmation after a successful PIN change.

func (*SMSAccountNotifier) NotifyPINReset

NotifyPINReset sends a confirmation after a successful PIN reset.

func (*SMSAccountNotifier) NotifyPINResetFailed

func (s *SMSAccountNotifier) NotifyPINResetFailed(ctx context.Context, n contracts.AccountNotification) error

NotifyPINResetFailed sends an alert when a PIN reset attempt fails.

func (*SMSAccountNotifier) NotifyPINWrongAttempt

func (s *SMSAccountNotifier) NotifyPINWrongAttempt(ctx context.Context, n contracts.AccountNotification) error

NotifyPINWrongAttempt sends a warning after an incorrect PIN entry.

func (*SMSAccountNotifier) NotifyRegistrationFailed

func (s *SMSAccountNotifier) NotifyRegistrationFailed(ctx context.Context, n contracts.AccountNotification) error

NotifyRegistrationFailed sends an alert when registration cannot be completed.

func (*SMSAccountNotifier) NotifyRegistrationSuccess

func (s *SMSAccountNotifier) NotifyRegistrationSuccess(ctx context.Context, n contracts.AccountNotification) error

NotifyRegistrationSuccess sends a welcome message after successful registration.

type SMSLoanNotifier

type SMSLoanNotifier struct {
	// contains filtered or unexported fields
}

SMSLoanNotifier implements contracts.LoanNotifier using a Notifier transport and per-language LoanTemplates.

func NewSMSLoanNotifier

func NewSMSLoanNotifier(notifier Notifier, opts ...LoanOption) (*SMSLoanNotifier, error)

NewSMSLoanNotifier creates a new SMSLoanNotifier over the built-in localized templates (en/sw/fr), adjusted by any options.

Every merged template is rendered against SentinelLoanNotification before the notifier is returned, so an unset or non-GSM-7 message fails here rather than on a borrower's handset.

func (*SMSLoanNotifier) NotifyLoanApproved

func (s *SMSLoanNotifier) NotifyLoanApproved(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanCashPickupApproved added in v1.0.0

func (s *SMSLoanNotifier) NotifyLoanCashPickupApproved(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanCashPickupCancelled added in v1.0.0

func (s *SMSLoanNotifier) NotifyLoanCashPickupCancelled(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanCashPickupInitiated added in v1.0.0

func (s *SMSLoanNotifier) NotifyLoanCashPickupInitiated(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanCashPickupReady added in v1.0.0

func (s *SMSLoanNotifier) NotifyLoanCashPickupReady(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanDisbursed

func (s *SMSLoanNotifier) NotifyLoanDisbursed(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanFailed

func (s *SMSLoanNotifier) NotifyLoanFailed(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanOffRampFailed added in v1.0.0

func (s *SMSLoanNotifier) NotifyLoanOffRampFailed(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanRejected

func (s *SMSLoanNotifier) NotifyLoanRejected(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanRepaid added in v1.5.0

func (s *SMSLoanNotifier) NotifyLoanRepaid(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyLoanStatement added in v1.1.1

func (s *SMSLoanNotifier) NotifyLoanStatement(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentExpired added in v1.1.2

func (s *SMSLoanNotifier) NotifyRepaymentExpired(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentFailed added in v1.1.2

func (s *SMSLoanNotifier) NotifyRepaymentFailed(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentInitiated added in v1.1.2

func (s *SMSLoanNotifier) NotifyRepaymentInitiated(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentMoreInfo added in v1.1.2

func (s *SMSLoanNotifier) NotifyRepaymentMoreInfo(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentPaybill added in v1.4.1

func (s *SMSLoanNotifier) NotifyRepaymentPaybill(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentReceived

func (s *SMSLoanNotifier) NotifyRepaymentReceived(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentReference added in v1.1.2

func (s *SMSLoanNotifier) NotifyRepaymentReference(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentReminder

func (s *SMSLoanNotifier) NotifyRepaymentReminder(ctx context.Context, n contracts.LoanNotification) error

func (*SMSLoanNotifier) NotifyRepaymentWindowExpiring added in v1.1.2

func (s *SMSLoanNotifier) NotifyRepaymentWindowExpiring(ctx context.Context, n contracts.LoanNotification) error

type SMSNotifier

type SMSNotifier struct {
	// contains filtered or unexported fields
}

SMSNotifier implements Notifier by delegating to an sms.SMSProvider.

func NewSMSNotifier

func NewSMSNotifier(provider sms.SMSProvider, from string) *SMSNotifier

NewSMSNotifier creates a new SMSNotifier.

func (*SMSNotifier) Send

func (n *SMSNotifier) Send(ctx context.Context, to string, message string) error

Send sends a single SMS message.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL