Documentation
¶
Overview ¶
Package contracts holds the interface seams between the platform's modules, together with the data types those interfaces exchange. It is a dependency-free boundary layer: producers and consumers both import contracts and depend on its abstractions rather than on each other, which keeps implementations swappable and avoids import cycles.
The interfaces ¶
Lender (lending.go) is the loan port — eligibility assessment, origination, and retrieval. The package that originates loans implements it; handlers and jobs depend only on the interface.
DisbursementUpdater (disbursement.go) is the off-ramp settlement port — terminal status transitions, borrower notifications and vault repayment for a loan identified by its sequence ID. The lending module implements it; the YellowCard webhook service, its refund poller and the MoneyGram poller consume it.
LoanNotifier (lending.go) and AccountNotifier (pin.go) are the notification ports. They are implemented by the notification layer, which may deliver over SMS, push, or any other transport, and are consumed by the services that need to tell a user about loan lifecycle events (approved, disbursed, off-ramp failed, cash-pickup ready, repayment received) or account and PIN events (registration, wrong-PIN attempts, lockout, PIN change and reset).
The data types ¶
EligibilityRequest and EligibilityResult frame an eligibility check; CreateLoanRequest and LoanRecord cover origination. A LoanRecord tracks both sides of a loan: the on-chain vault disbursement and the off-ramp settlement, including the settlement method, disbursement status, and provider reference. LoanNotification and AccountNotification carry the fields each notification method needs — not every field applies to every method. Monetary amounts are in stroops (USDC times ten million); display amounts and currencies travel alongside them on the notification types. Sentinel errors live in lending_errors.go.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( ErrNotEligible = errors.New("user is not eligible for a loan") ErrInsufficientLiquidity = errors.New("insufficient vault liquidity") ErrLoanNotFound = errors.New("loan not found") ErrLoanNotActive = errors.New("loan is not in an active state") ErrAccountNotFound = errors.New("account not found") ErrAccountNotOwned = errors.New("account is not owned by the user") )
var ErrVaultRepayDeferred = errors.New("vault repay deferred")
ErrVaultRepayDeferred is returned by RepayVault and RepayVaultAmount when the repay was not attempted: another caller holds it, its outcome is unknown, or it has exhausted its inline attempts and belongs to the reconciler.
Functions ¶
This section is empty.
Types ¶
type AccountNotification ¶
type AccountNotification struct {
// UserID is the unique identifier of the user being notified.
UserID string
// PhoneNumber is the destination for SMS delivery (E.164 format).
PhoneNumber string
// FullName is used in registration welcome messages.
FullName string
// RemainingAttempts indicates how many PIN attempts remain before lockout.
// Used by NotifyPINWrongAttempt.
RemainingAttempts int
// LockedUntil is a human-readable duration string (e.g. "15 minutes")
// indicating how long until the lockout expires. Used by NotifyAccountLocked.
LockedUntil string
// Reason provides a human-readable explanation for failure notifications.
Reason string
// Language optionally pins the SMS language (ISO code: en/sw/fr). When
// empty the notifier resolves it from the recipient's stored preference.
Language string
}
AccountNotification carries the data needed to notify a user about account and PIN lifecycle events. Not all fields are used by every notification method; see individual method documentation for which fields are relevant.
type AccountNotifier ¶
type AccountNotifier interface {
// NotifyRegistrationSuccess informs a user that their account has been
// created and their PIN is set.
NotifyRegistrationSuccess(ctx context.Context, n AccountNotification) error
// NotifyRegistrationFailed informs a user that their registration could
// not be completed, along with a human-readable reason.
NotifyRegistrationFailed(ctx context.Context, n AccountNotification) error
// NotifyPINWrongAttempt warns a user that an incorrect PIN was entered
// and includes the number of remaining attempts before lockout.
NotifyPINWrongAttempt(ctx context.Context, n AccountNotification) error
// NotifyAccountLocked informs a user that their account has been
// temporarily locked due to repeated failed PIN attempts.
NotifyAccountLocked(ctx context.Context, n AccountNotification) error
// NotifyPINChanged confirms that the user's PIN was changed successfully.
NotifyPINChanged(ctx context.Context, n AccountNotification) error
// NotifyPINChangeFailed alerts a user that a PIN change attempt was
// unsuccessful, along with the reason.
NotifyPINChangeFailed(ctx context.Context, n AccountNotification) error
// NotifyPINReset confirms that the user's PIN was reset successfully
// via the recovery flow.
NotifyPINReset(ctx context.Context, n AccountNotification) error
// NotifyPINResetFailed alerts a user that a PIN reset attempt failed,
// along with the reason.
NotifyPINResetFailed(ctx context.Context, n AccountNotification) error
}
AccountNotifier defines the interface for sending account and PIN lifecycle notifications to users. Implementations may deliver via SMS, push, email, or any other transport. Methods must be safe for concurrent use.
type CompletionFinancials ¶ added in v1.7.0
type CompletionFinancials struct {
ConvertedAmountLocal float64
ServiceFeeAmountUSD float64
ServiceFeeAmountLocal float64
PartnerFeeAmountUSD float64
PartnerFeeAmountLocal float64
}
CompletionFinancials carries the final amounts of a completed off-ramp payment, looked up at completion time.
type CreateLoanRequest ¶
type CreateLoanRequest struct {
UserID string
AccountID string
PrincipalAmount int64 // In stroops
PrincipalAsset string // e.g. "USDC"
InterestRateBps int32 // Basis points
DurationDays int
RepaymentSchedule string // "daily", "weekly", "bi_weekly", "monthly", "lump_sum"
}
CreateLoanRequest contains the data needed to create a new loan.
type DisbursementUpdater ¶ added in v1.7.0
type DisbursementUpdater interface {
// UpdateDisbursementStatus sets the disbursement status of the loan
// identified by sequenceID.
UpdateDisbursementStatus(ctx context.Context, sequenceID, status string) error
// RecordDisbursementCompletion persists the final financials of a
// completed payment: the delivered local amount and the fees.
RecordDisbursementCompletion(ctx context.Context, sequenceID string, fin CompletionFinancials) error
// SetSettlementMethod updates the loan's settlement method. The refund
// poller calls it when a direct-mode disbursement fails over to fiat, so
// the eventual completion triggers the vault repay branch.
SetSettlementMethod(ctx context.Context, sequenceID, method string) error
// IsDirectSettlement reports whether the loan was disbursed via direct
// settlement, which decides whether a failed payout waits for a refund.
IsDirectSettlement(ctx context.Context, sequenceID string) (bool, error)
// NotifyDisbursementComplete tells the borrower their disbursement
// completed.
NotifyDisbursementComplete(ctx context.Context, sequenceID string) error
// NotifyDisbursementFailed tells the borrower their disbursement failed.
NotifyDisbursementFailed(ctx context.Context, sequenceID string) error
// NotifyCashPickupReady tells the borrower their cash is collectable and
// quotes the MoneyGram reference number.
NotifyCashPickupReady(ctx context.Context, sequenceID string) error
// NotifyRefundReceived tells the borrower their cash pickup was cancelled
// and the funds returned, and that they can request again.
NotifyRefundReceived(ctx context.Context, sequenceID string) error
// RepayVault returns the borrowed principal from treasury to the vault.
// No-op if already repaid.
RepayVault(ctx context.Context, sequenceID string) error
// RepayVaultAmount repays an explicit stroop amount rather than the
// principal. Used for refunds, where the anchor may return less than was
// sent and repaying the principal would overdraw the treasury.
RepayVaultAmount(ctx context.Context, sequenceID string, amountStroops int64) error
// LoanRefs resolves the loan behind sequenceID, for callers that only
// know the off-ramp sequence and need the loan's identity on a log line.
LoanRefs(ctx context.Context, sequenceID string) (loanID, reference string, err error)
}
DisbursementUpdater drives a loan's disbursement through its terminal states and the notifications and vault repayments that go with them. The YellowCard webhook service, its refund poller and the MoneyGram poller all depend on it; the lending module implements it once for every off-ramp.
type EligibilityRequest ¶
type EligibilityRequest struct {
UserID string
Amount int64 // In stroops (USDC * 10^7)
DurationDays int
}
EligibilityRequest contains the data needed to assess loan eligibility.
type EligibilityResult ¶
type EligibilityResult struct {
Approved bool
Reason string
MaxAmount int64 // Maximum eligible amount in stroops
InterestRate float64 // Annual interest rate as a decimal (e.g. 0.12 = 12%)
}
EligibilityResult contains the outcome of an eligibility assessment.
type Lender ¶
type Lender interface {
AssessEligibility(ctx context.Context, req EligibilityRequest) (*EligibilityResult, error)
CreateLoan(ctx context.Context, req CreateLoanRequest) (*LoanRecord, error)
GetLoan(ctx context.Context, loanID string) (*LoanRecord, error)
GetUserLoans(ctx context.Context, userID string) ([]*LoanRecord, error)
}
Lender defines the interface for loan origination and retrieval.
type LoanNotification ¶
type LoanNotification struct {
LoanID string
LoanReference string
UserID string
PhoneNumber string
Amount int64 // Raw amount (stroops/cents) for consumers that need it
DisplayAmount float64 // Display-ready (e.g. 5000.00)
DisplayCurrency string // e.g. "KES", "USD"
Status string
Reason string // For rejection notifications
RemainingBalance float64 // For repayment confirmations
DueDate *time.Time // For repayment reminders
// InteractiveURL is the MoneyGram SEP-24 webview URL sent in the
// cash-pickup initiated SMS. Empty for non-MG notifications.
InteractiveURL string
// CashPickupRef is the reference number the user quotes at the MG agent
// when collecting cash. Populated once MG locks the payout.
CashPickupRef string
// CashPickupInfoURL is MoneyGram's support deep-link for the transaction,
// sent alongside the reference so a borrower with a problem at the agent
// has somewhere to go. Unlike InteractiveURL it stays valid after the
// withdrawal settles.
CashPickupInfoURL string
// PaybillNumber is the M-Pesa collection shortcode the borrower pays under.
// Only set on the paybill-instructions notification.
PaybillNumber string
// RepaymentExpiresAt is when an opened cash deposit lapses. Only set on
// the repayment notifications; nil elsewhere.
RepaymentExpiresAt *time.Time
// Language optionally pins the SMS language (ISO code: en/sw/fr). When
// empty the notifier resolves it from the recipient's stored preference.
Language string
}
LoanNotification contains data for loan lifecycle notifications.
type LoanNotifier ¶
type LoanNotifier interface {
NotifyLoanApproved(ctx context.Context, n LoanNotification) error
NotifyLoanRejected(ctx context.Context, n LoanNotification) error
NotifyLoanDisbursed(ctx context.Context, n LoanNotification) error
NotifyLoanFailed(ctx context.Context, n LoanNotification) error
// NotifyLoanOffRampFailed reports that vault borrow succeeded but fiat
// disbursement could not be completed and the USDC has been returned to
// the vault. Distinct from NotifyLoanFailed, which is a credit-default
// notice.
NotifyLoanOffRampFailed(ctx context.Context, n LoanNotification) error
// NotifyLoanCashPickupApproved acknowledges approval of a cash-pickup
// loan without implying a push disbursement is on the way. A subsequent
// NotifyLoanCashPickupInitiated carries the MoneyGram interactive URL.
NotifyLoanCashPickupApproved(ctx context.Context, n LoanNotification) error
// NotifyLoanRepaid confirms the treasury-to-vault leg confirmed and the
// loan is closed. Distinct from NotifyRepaymentReceived, which fires
// earlier — once cash reached the treasury but before the vault leg is
// known to have settled.
NotifyLoanRepaid(ctx context.Context, n LoanNotification) error
NotifyRepaymentReceived(ctx context.Context, n LoanNotification) error
NotifyRepaymentReminder(ctx context.Context, n LoanNotification) error
// NotifyRepaymentFailed reports that a cash deposit could not be opened.
//
// The USSD screen has already told the borrower to expect an SMS by the
// time this fires — initiation runs in the background because it is far
// slower than a USSD session lives — so silence here would strand them
// waiting for a message that is never coming.
NotifyRepaymentFailed(ctx context.Context, n LoanNotification) error
// NotifyRepaymentReference carries the reference MoneyGram issues once the
// borrower commits in the webview. It is what they quote at the agent
// counter to hand cash over, so without it the repayment cannot complete
// however ready everything else is.
NotifyRepaymentReference(ctx context.Context, n LoanNotification) error
// NotifyRepaymentMoreInfo carries MoneyGram's transaction page when no
// reference has been issued yet.
//
// SEP-24 defines external_transaction_id as the ID of the external
// transaction that "started the deposit", so for a cash-in it only exists
// once the borrower has paid — after the point the code would have been
// useful. more_info_url is the field the spec designates for telling a user
// how to start a deposit, and it is populated from the first poll. Read from
// InteractiveURL.
NotifyRepaymentMoreInfo(ctx context.Context, n LoanNotification) error
// NotifyRepaymentPaybill sends the paybill instructions for a mobile-money
// repayment: shortcode, account reference, and amount. Read from
// PaybillNumber, LoanReference, and DisplayAmount/DisplayCurrency.
NotifyRepaymentPaybill(ctx context.Context, n LoanNotification) error
// NotifyRepaymentInitiated 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 way the link reaches
// them.
//
// Distinct from NotifyLoanCashPickupInitiated, which carries a link to
// collect money. This one carries a link to hand money over — the opposite
// message, and the borrower must not confuse the two.
NotifyRepaymentInitiated(ctx context.Context, n LoanNotification) error
// NotifyRepaymentWindowExpiring warns that an opened cash deposit is about
// to lapse. Sent once, and distinct from NotifyRepaymentReminder, which is
// driven by the loan's due date rather than by a deposit window.
NotifyRepaymentWindowExpiring(ctx context.Context, n LoanNotification) error
// NotifyRepaymentExpired tells the borrower an opened deposit lapsed
// unused and that they can start again. The loan is untouched: nothing was
// paid, so this is not a default notice.
NotifyRepaymentExpired(ctx context.Context, n LoanNotification) error
// NotifyLoanStatement sends the borrower a record of one loan —
// reference, amount and due date. Unlike the other methods this is
// user-initiated rather than lifecycle-driven: it answers a "My Loans"
// request, so it is sent on demand and repeats are expected.
NotifyLoanStatement(ctx context.Context, n LoanNotification) error
// NotifyLoanCashPickupInitiated sends the MoneyGram interactive URL to
// the user. The locked payout amount is unknown at this point and
// confirmed when the user opens the link; the template should make that
// disclosure explicit.
NotifyLoanCashPickupInitiated(ctx context.Context, n LoanNotification) error
// NotifyLoanCashPickupReady sends the cash-pickup reference number once
// MoneyGram has locked the payout. Template should include the
// reference, the locked amount, and the currency.
NotifyLoanCashPickupReady(ctx context.Context, n LoanNotification) error
// NotifyLoanCashPickupCancelled tells the borrower their cash pickup was
// cancelled and the funds returned. Distinct from NotifyLoanOffRampFailed:
// the usual cause is the borrower cancelling in MoneyGram's own app, so
// this reads as an acknowledgement rather than a failure.
NotifyLoanCashPickupCancelled(ctx context.Context, n LoanNotification) error
}
LoanNotifier defines the interface for sending loan lifecycle notifications.
type LoanRecord ¶
type LoanRecord struct {
ID string
LoanReference string
UserID string
AccountID string
PrincipalAmount int64
PrincipalAsset string
InterestRateBps int32
TotalAmount int64
DurationDays int
RepaymentSchedule string
DueDate *time.Time
Status string // "pending", "approved", "disbursed", "repaid", "defaulted", "cancelled"
VaultTxHash string
RampProvider string
RampRequestID string
RampSequenceID string
RampFiatAmount int64
RampFiatCurrency string
SettlementMethod string // "direct" or "fiat"
DisbursementStatus string // "pending", "crypto_sent", "processing", "complete", "refund_pending", "failed"
CreatedAt time.Time
UpdatedAt time.Time
}
LoanRecord represents a loan with vault and ramp disbursement tracking.