models

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: 6 Imported by: 0

Documentation

Overview

Package models holds the GORM persistence models — the database schema expressed as Go structs. Each type maps to one table and carries the JSON and gorm struct tags that define its API shape and its columns. The repository layer reads and writes these; the service layers wrap them in their own DTOs.

The entities are User, Account, and Transaction, plus SecurityQuestion for the PIN-recovery flow. An Account is a Stellar child account belonging to a User; a Transaction optionally references a User and an Account. PIN material lives on the User (its hash, attempt count, and lockout time), and security-question answers are stored hashed.

Conventions

Every model follows the same rules. The primary key is a UUIDv7 assigned in a BeforeCreate hook, so IDs are unique and time-ordered without a database sequence. A TableName method pins the table name. Secret fields — PIN hash and security-answer hash — are tagged json:"-" so they never serialize into a response. User and Account carry a nullable, indexed DeletedAt that the repository stamps and clears by hand for reversible soft deletes; transactions are never deleted.

Canonical enums

The string constants defined here are the single source of truth for the values the service state machines validate against: KYC statuses, the shared user and account lifecycle statuses, and the transaction statuses, categories, and types. Referencing these constants rather than bare strings keeps the services and the stored rows in agreement.

Index

Constants

View Source
const (
	// Transaction Status
	TxStatusPending   = "pending"
	TxStatusSubmitted = "submitted"
	TxStatusSuccess   = "success"
	TxStatusFailed    = "failed"
	TxStatusCancelled = "cancelled"

	// Transaction Types — Loan Disbursement
	TxTypeVaultBorrow  = "vault_borrow"  // USDC borrowed from Stellar vault to user account
	TxTypeOffRamp      = "off_ramp"      // Off-ramp initiated (crypto-to-fiat via YellowCard)
	TxTypeFiatFailover = "fiat_failover" // Fiat failover after direct settlement refund
	TxTypeVaultRepay   = "vault_repay"   // USDC repaid from treasury back to Stellar vault
	TxTypeRefund       = "refund"        // USDC returned by an anchor after a cancelled off-ramp

	// TxTypeAnchorTransfer is the on-chain USDC leg of an anchor withdrawal:
	// treasury to the anchor's withdraw account. Distinct from TxTypeOffRamp,
	// which records the off-chain fiat leg the anchor pays out afterwards — a
	// cash-pickup disbursement produces both.
	TxTypeAnchorTransfer = "anchor_transfer"

	// TxTypeLoanRepayment is the off-chain leg: cash the borrower hands over
	// at a MoneyGram agent, or pays in over a mobile-money paybill. It is what
	// the borrower did, not what settled — the USDC leg is separate and can
	// lag it or fail.
	TxTypeLoanRepayment = "loan_repayment"

	// TxTypeAnchorDeposit is the on-chain USDC leg of an anchor deposit:
	// the anchor to the treasury. The mirror of TxTypeAnchorTransfer.
	TxTypeAnchorDeposit = "anchor_deposit"
)
View Source
const (
	TxCategoryOnChain  = "on_chain"
	TxCategoryOffChain = "off_chain"
)

Transaction categories. Derived from TxType rather than stored: every type is settled either on the Stellar ledger or off it, never both, so a column would only add a way for the two to disagree.

View Source
const (
	// ChainStatusPending is set at registration: the sponsored-creation
	// transaction has been dispatched but not confirmed.
	ChainStatusPending = "pending"

	// ChainStatusConfirmed means the account was observed on the network.
	ChainStatusConfirmed = "confirmed"

	// ChainStatusFailed means creation retries were exhausted. Lending is
	// blocked for these until EnsureOnChainAccount heals them.
	ChainStatusFailed = "failed"

	// ChainStatusUnknown marks rows that predate this column.
	ChainStatusUnknown = "unknown"
)

Account.ChainStatus values. The account row is committed before the Stellar account is submitted, so this is what distinguishes a row whose keypair exists on-chain from one whose creation never landed.

View Source
const (
	// User KYC Status
	KYCStatusPending  = "pending"
	KYCStatusVerified = "verified"
	KYCStatusRejected = "rejected"
	KYCStatusExpired  = "expired"

	// User/Account Status
	StatusActive    = "active"
	StatusSuspended = "suspended"
	StatusBlocked   = "blocked"
	StatusFrozen    = "frozen"
	StatusClosed    = "closed"
)

Constants for status enums

View Source
const PredefinedQuestionCount = 5

PredefinedQuestionCount is the total number of predefined security questions available for selection. Question IDs range from 1 to PredefinedQuestionCount.

Variables

This section is empty.

Functions

func TxCategoryFor added in v1.0.0

func TxCategoryFor(txType string) string

TxCategoryFor reports where a transaction type settles.

Unknown types report off_chain: a type this function has not been taught about has no ledger presence to claim.

Types

type Account

type Account struct {
	ID           string     `json:"id" gorm:"type:uuid;primaryKey"`
	UserID       string     `json:"user_id" gorm:"type:uuid;not null;index"`
	PublicKey    string     `json:"public_key" gorm:"type:varchar(56);uniqueIndex;not null"`
	AccountIndex int        `json:"account_index" gorm:"not null"`
	Status       string     `json:"status" gorm:"type:varchar(20);not null;default:'active'"`
	ChainStatus  string     `json:"chain_status" gorm:"type:varchar(20);not null;default:'pending'"`
	CreatedAt    time.Time  `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt    time.Time  `json:"updated_at" gorm:"autoUpdateTime;not null"`
	DeletedAt    *time.Time `json:"deleted_at,omitempty" gorm:"index"`

	User User `gorm:"foreignKey:UserID"`
}

Account represents a Stellar child account

func (*Account) BeforeCreate

func (account *Account) BeforeCreate(tx *gorm.DB) error

BeforeCreate sets the ID before creating a new account

func (Account) TableName

func (Account) TableName() string

TableName specifies the table name for Account model

type AddressScreening added in v1.5.0

type AddressScreening struct {
	ID        string `json:"id" gorm:"type:uuid;primaryKey"`
	AddressID string `json:"address_id" gorm:"column:address_id;type:uuid;not null;index"`

	// EllipticAnalysisID/EllipticScreeningID let this screening be fetched
	// from Elliptic directly later, independent of RawPayload.
	EllipticAnalysisID  string `json:"elliptic_analysis_id,omitempty" gorm:"column:elliptic_analysis_id"`
	EllipticScreeningID string `json:"elliptic_screening_id,omitempty" gorm:"column:elliptic_screening_id"`

	ScreeningSource string `json:"screening_source" gorm:"column:screening_source;type:varchar(30);not null"`

	// RiskScore is nullable and never defaulted to zero — see
	// compliance.Screening.RiskScore's doc comment on why a null score is
	// not the same fact as a clean score.
	RiskScore  *float64 `json:"risk_score,omitempty" gorm:"column:risk_score;type:numeric"`
	Sanctioned bool     `json:"sanctioned" gorm:"not null;default:false"`
	Verdict    string   `json:"verdict" gorm:"type:varchar(20);not null"`

	// RawPayload is Elliptic's whole response. Kept because a compliance
	// record has to be reproducible years later, and a struct designed
	// today will not have a field for whatever Elliptic adds later.
	RawPayload datatypes.JSON `json:"raw_payload" gorm:"column:raw_payload;type:jsonb;not null"`

	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;not null"`
}

AddressScreening is one screening performed on a CounterpartyAddress. Append-only — there is deliberately no Update path anywhere in this codebase for this table. A compliance record that changes in place cannot answer "what did we know on the day we approved this," which is the only question that matters after an incident. See the source design doc §12.

func (*AddressScreening) BeforeCreate added in v1.5.0

func (s *AddressScreening) BeforeCreate(g *gorm.DB) error

func (AddressScreening) TableName added in v1.5.0

func (AddressScreening) TableName() string

type AirtelTransaction added in v1.6.0

type AirtelTransaction struct {
	ID string `json:"id" gorm:"type:uuid;primaryKey"`

	// PartnerTxnID is the id we generated and sent. Unique-indexed, and the
	// idempotency key — Airtel's own receipt cannot be, because it does not
	// exist until the transaction succeeds.
	PartnerTxnID string `json:"partner_txn_id" gorm:"column:partner_txn_id;type:varchar(64);uniqueIndex;not null"`

	// AirtelMoneyID is Airtel's receipt, minted only on TS. It is the only
	// key a refund accepts, which is why a refund is impossible until an
	// enquiry or a callback has disclosed it.
	AirtelMoneyID *string `json:"airtel_money_id,omitempty" gorm:"column:airtel_money_id;type:varchar(64)"`

	Source     AirtelTransactionSource `json:"source" gorm:"type:varchar(20);not null;index"`
	StatusCode string                  `json:"status_code" gorm:"type:varchar(4);not null"`

	Confirmed    bool                         `json:"confirmed" gorm:"not null;default:false"`
	ConfirmedVia *AirtelTransactionConfirmVia `json:"confirmed_via,omitempty" gorm:"type:varchar(20)"`

	// HashVerified records whether the callback's HmacSHA256 checked out.
	// HashVariant records which reading of "the callback body" matched, so
	// the answer the portal does not give is recorded from live traffic
	// rather than inferred.
	HashVerified bool    `json:"hash_verified" gorm:"not null;default:false"`
	HashVariant  *string `json:"hash_variant,omitempty" gorm:"type:varchar(20)"`

	Reference string  `json:"reference" gorm:"type:varchar(25);index"`
	LoanID    *string `json:"loan_id,omitempty" gorm:"type:uuid;index"`

	// AmountMinor is in minor units throughout. The M-Pesa rail's equivalent
	// column is named for shillings and stores them; naming this one for the
	// unit it holds removes the question.
	AmountMinor int64 `json:"amount_minor" gorm:"column:amount_minor;not null"`

	// AppliedStroops is set once, the first time this row is converted to
	// USDC and credited toward LoanID's repayment progress.
	AppliedStroops *int64 `json:"applied_stroops,omitempty" gorm:"column:applied_stroops"`

	// Msisdn is the payer's number in Airtel's national form. Nullable PII;
	// never log it. Airtel does not mask it the way a C2B confirmation does,
	// so there is no masked counterpart and no safe-to-log version.
	Msisdn    *string `json:"msisdn,omitempty" gorm:"type:varchar(25)"`
	PayerName *string `json:"payer_name,omitempty" gorm:"type:varchar(100)"`

	TransTime time.Time `json:"trans_time" gorm:"not null"`

	// RawPayload is the bytes as received. It is not optional: when an
	// enquiry disagrees with a callback, the bytes settle it — and with two
	// callbacks per transaction, which bytes arrived when is the only record
	// of the intermediate state.
	RawPayload datatypes.JSON `json:"raw_payload" gorm:"type:jsonb;not null"`

	NextPollAt   *time.Time `json:"next_poll_at,omitempty" gorm:"index"`
	PollAttempts int        `json:"poll_attempts" gorm:"not null;default:0"`

	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime;not null"`
}

AirtelTransaction is the observation log for inbound Airtel Money notifications.

A row is never evidence of a payment. A verified hash proves Airtel sent the callback; it does not prove the payment settled, and an intermediate callback looks exactly like a final one. The row becomes a payment only once ConfirmedVia names an enquiry.

func (*AirtelTransaction) BeforeCreate added in v1.6.0

func (tx *AirtelTransaction) BeforeCreate(_ *gorm.DB) error

BeforeCreate sets the ID before creating a new AirtelTransaction.

func (AirtelTransaction) TableName added in v1.6.0

func (AirtelTransaction) TableName() string

TableName specifies the table name for AirtelTransaction.

type AirtelTransactionConfirmVia added in v1.6.0

type AirtelTransactionConfirmVia string

AirtelTransactionConfirmVia is how an observation was independently verified. A callback is not on this list, and that is the point: Airtel's callback carries intermediate or final status with nothing to tell them apart, so it can never be the thing that confirms.

const (
	AirtelConfirmViaEnquiry AirtelTransactionConfirmVia = "enquiry"
	AirtelConfirmViaSummary AirtelTransactionConfirmVia = "summary"
)

The confirmation paths.

type AirtelTransactionSource added in v1.6.0

type AirtelTransactionSource string

AirtelTransactionSource is where an observation came from.

const (
	AirtelSourceCallback AirtelTransactionSource = "callback"
	AirtelSourceEnquiry  AirtelTransactionSource = "enquiry"
	AirtelSourceSummary  AirtelTransactionSource = "summary"
)

The inbound sources.

type Counterparty added in v1.5.0

type Counterparty struct {
	ID string `json:"id" gorm:"type:uuid;primaryKey"`

	LegalName          string `json:"legal_name" gorm:"type:varchar(200);not null"`
	RegistrationNumber string `json:"registration_number,omitempty" gorm:"type:varchar(100)"`
	Jurisdiction       string `json:"jurisdiction,omitempty" gorm:"type:varchar(100)"`

	KYBStatus CounterpartyKYBStatus `json:"kyb_status" gorm:"column:kyb_status;type:varchar(20);not null;default:'pending';index"`

	// EllipticCustomerReference is sent as customer_reference on every
	// screening call for this counterparty's addresses — set once at
	// creation, never changed. See the source design doc §4.
	EllipticCustomerReference string `json:"elliptic_customer_reference" gorm:"column:elliptic_customer_reference;type:varchar(100);uniqueIndex;not null"`

	KYBApprovedAt *time.Time `json:"kyb_approved_at,omitempty" gorm:"column:kyb_approved_at"`
	// KYBApprovedBy is the approving admin's Stellar public key, as text —
	// see counterparty_repository.go's doc comment on why this is text and
	// not a user-UUID FK.
	KYBApprovedBy *string `json:"kyb_approved_by,omitempty" gorm:"column:kyb_approved_by"`

	Addresses []CounterpartyAddress `json:"addresses,omitempty" gorm:"foreignKey:CounterpartyID"`

	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime;not null"`
}

Counterparty is an institutional vault depositor undergoing KYB — external and self-custodied, the opposite of the custodial borrower accounts in models.Account. See elliptic-compliance-integration.md §1.

func (*Counterparty) BeforeCreate added in v1.5.0

func (c *Counterparty) BeforeCreate(g *gorm.DB) error

BeforeCreate assigns an ID only if the caller hasn't already set one — unlike every other model's BeforeCreate in this codebase, which always overwrites. Callers that need the ID before the insert (to derive EllipticCustomerReference from it, so the row is never briefly written with an empty value that would collide with the column's unique index) pre-generate it with uuid.NewV7() themselves.

func (Counterparty) TableName added in v1.5.0

func (Counterparty) TableName() string

type CounterpartyAddress added in v1.5.0

type CounterpartyAddress struct {
	ID             string `json:"id" gorm:"type:uuid;primaryKey"`
	CounterpartyID string `json:"counterparty_id" gorm:"column:counterparty_id;type:uuid;not null;index"`
	// Address is a Stellar G... account, 56 characters, matching
	// accounts.public_key.
	Address string `json:"address" gorm:"type:varchar(56);uniqueIndex;not null"`

	Status CounterpartyAddressStatus `json:"status" gorm:"type:varchar(20);not null;default:'pending';index"`

	LastScreeningID *string    `json:"last_screening_id,omitempty" gorm:"column:last_screening_id;type:uuid"`
	ScreenedAt      *time.Time `json:"screened_at,omitempty" gorm:"column:screened_at"`
	ExpiresAt       *time.Time `json:"expires_at,omitempty" gorm:"column:expires_at;index"`

	OnchainState CounterpartyAddressOnchainState `json:"onchain_state" gorm:"column:onchain_state;type:varchar(20);not null;default:'absent';index"`

	ApprovedBy     *string    `json:"approved_by,omitempty" gorm:"column:approved_by"`
	ApprovedAt     *time.Time `json:"approved_at,omitempty" gorm:"column:approved_at"`
	RevokedBy      *string    `json:"revoked_by,omitempty" gorm:"column:revoked_by"`
	RevokedAt      *time.Time `json:"revoked_at,omitempty" gorm:"column:revoked_at"`
	OverrideReason *string    `json:"override_reason,omitempty" gorm:"column:override_reason"`

	Counterparty *Counterparty `json:"counterparty,omitempty" gorm:"foreignKey:CounterpartyID"`
	// LatestScreening is a belongs-to on last_screening_id — an ordinary FK,
	// so Preload("LatestScreening") works the same as any other association.
	LatestScreening *AddressScreening `json:"latest_screening,omitempty" gorm:"foreignKey:LastScreeningID;references:ID"`

	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime;not null"`
}

CounterpartyAddress is a wallet address a counterparty has submitted for screening — the allowlist's source of truth, and the row an approval or revocation is actually attached to.

func (*CounterpartyAddress) BeforeCreate added in v1.5.0

func (a *CounterpartyAddress) BeforeCreate(g *gorm.DB) error

func (CounterpartyAddress) TableName added in v1.5.0

func (CounterpartyAddress) TableName() string

type CounterpartyAddressOnchainState added in v1.5.0

type CounterpartyAddressOnchainState string

CounterpartyAddressOnchainState tracks whether the vault contract's on-chain allowlist actually reflects this row — a database write and a contract write are two systems, and one can fail after the other succeeds. See the source design doc §12.

const (
	OnchainStateAbsent   CounterpartyAddressOnchainState = "absent"
	OnchainStatePending  CounterpartyAddressOnchainState = "pending"
	OnchainStateApproved CounterpartyAddressOnchainState = "approved"
	OnchainStateRevoked  CounterpartyAddressOnchainState = "revoked"
)

type CounterpartyAddressStatus added in v1.5.0

type CounterpartyAddressStatus string

CounterpartyAddressStatus is the address's current compliance state — the §10 verdict states, plus "expired" (a staleness state no single screening produces; it's derived from expires_at). Deliberately its own type rather than reusing compliance.Verdict: this column is what we currently believe about the address, which "expired" can describe even when the last screening's own verdict was "approved".

const (
	AddressStatusPending      CounterpartyAddressStatus = "pending"
	AddressStatusApproved     CounterpartyAddressStatus = "approved"
	AddressStatusRejected     CounterpartyAddressStatus = "rejected"
	AddressStatusReview       CounterpartyAddressStatus = "review"
	AddressStatusUnscreenable CounterpartyAddressStatus = "unscreenable"
	AddressStatusExpired      CounterpartyAddressStatus = "expired"
)

type CounterpartyKYBStatus added in v1.5.0

type CounterpartyKYBStatus string

CounterpartyKYBStatus is where a KYB entity stands in its own onboarding, independent of any address's screening status.

const (
	CounterpartyKYBPending  CounterpartyKYBStatus = "pending"
	CounterpartyKYBApproved CounterpartyKYBStatus = "approved"
	CounterpartyKYBRejected CounterpartyKYBStatus = "rejected"
	CounterpartyKYBExpired  CounterpartyKYBStatus = "expired"
)

type Date added in v1.0.0

type Date struct{ time.Time }

func (Date) MarshalJSON added in v1.0.0

func (d Date) MarshalJSON() ([]byte, error)

func (*Date) Scan added in v1.0.0

func (d *Date) Scan(value any) error

Scan reads a DATE value from the database into d.

func (Date) Value added in v1.0.0

func (d Date) Value() (driver.Value, error)

Value renders the date as a DATE literal for the database driver. Without this, GORM serializes the embedded time.Time via String(), which Postgres rejects for a DATE column.

type MpesaNumberValidation added in v1.4.1

type MpesaNumberValidation struct {
	ID string `json:"id" gorm:"type:uuid;primaryKey"`

	// IdentityHash is sha256(msisdn|idType|idNumber), hex-encoded.
	IdentityHash string `json:"identity_hash" gorm:"column:identity_hash;type:varchar(64);uniqueIndex;not null"`

	Matched      bool   `json:"matched" gorm:"not null"`
	ResponseCode string `json:"response_code" gorm:"column:response_code;type:varchar(10);not null"`

	CheckedAt time.Time `json:"checked_at" gorm:"column:checked_at;not null"`
}

MpesaNumberValidation caches the verdict of a Mobile Number Validation call, keyed on a hash of the (msisdn, idType, idNumber) tuple so the cache itself never stores the PII it exists to avoid re-checking.

func (*MpesaNumberValidation) BeforeCreate added in v1.4.1

func (v *MpesaNumberValidation) BeforeCreate(g *gorm.DB) error

BeforeCreate sets the ID before creating a new MpesaNumberValidation.

func (MpesaNumberValidation) TableName added in v1.4.1

func (MpesaNumberValidation) TableName() string

TableName specifies the table name for MpesaNumberValidation.

type MpesaTransaction added in v1.4.1

type MpesaTransaction struct {
	ID string `json:"id" gorm:"type:uuid;primaryKey"`

	// TransID is the M-Pesa receipt. Unique-indexed. The idempotency key.
	TransID string `json:"trans_id" gorm:"column:trans_id;type:varchar(20);uniqueIndex;not null"`

	Source       MpesaTransactionSource      `json:"source" gorm:"type:varchar(20);not null;index"`
	Confirmed    bool                        `json:"confirmed" gorm:"not null;default:false"`
	ConfirmedVia *MpesaTransactionConfirmVia `json:"confirmed_via,omitempty" gorm:"type:varchar(20)"`

	BillRefNumber string  `json:"bill_ref_number" gorm:"type:varchar(20);index"`
	LoanID        *string `json:"loan_id,omitempty" gorm:"type:uuid;index"`

	AmountKes int64 `json:"amount_kes" gorm:"column:amount_kes;not null"` // minor units

	// AppliedStroops is set once, the first time this row is converted to
	// USDC and credited toward LoanID's repayment progress — nil means not
	// yet applied. See the migration's comment for why this lives on the
	// row rather than a running total elsewhere.
	AppliedStroops *int64 `json:"applied_stroops,omitempty" gorm:"column:applied_stroops"`

	// MsidnMasked comes from a C2B callback; MsidnFull comes from the Pull
	// reconciler. Full is nullable PII; never log it.
	MsidnMasked string  `json:"msisdn_masked,omitempty" gorm:"column:msisdn_masked"`
	MsidnFull   *string `json:"msisdn_full,omitempty" gorm:"column:msisdn_full"`

	// PayerName comes from the C2B callback or a reversal result. Nullable PII.
	PayerName *string `json:"payer_name,omitempty" gorm:"type:varchar(100)"`

	TransTime time.Time `json:"trans_time" gorm:"not null"`

	// ThirdPartyTransID is our own correlation handle, echoed validation →
	// confirmation.
	ThirdPartyTransID *string `json:"third_party_trans_id,omitempty"`

	CheckoutRequestID *string `json:"checkout_request_id,omitempty" gorm:"index"` // STK
	MerchantRequestID *string `json:"merchant_request_id,omitempty"`              // STK
	SequenceID        *string `json:"sequence_id,omitempty" gorm:"type:varchar(200);index"`

	// RawPayload is the bytes as received. It is not optional: when a
	// reconciliation disagrees with a callback, the bytes settle it.
	RawPayload datatypes.JSON `json:"raw_payload" gorm:"type:jsonb;not null"`

	Reversal   MpesaTransactionReversal `json:"reversal_state" gorm:"column:reversal_state;type:varchar(20);not null;default:'none'"`
	NextPollAt *time.Time               `json:"next_poll_at,omitempty" gorm:"index"`

	CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime;not null"`
}

MpesaTransaction is the observation log for inbound M-Pesa notifications. Every callback lands here before anything else happens, so the confirm-before-credit discipline is enforceable.

A row is never evidence of a payment. Daraja signs nothing, so a well-formed callback proves only that something posted to a URL. It becomes a payment only after ConfirmedVia names an independent check.

func (*MpesaTransaction) BeforeCreate added in v1.4.1

func (tx *MpesaTransaction) BeforeCreate(g *gorm.DB) error

BeforeCreate sets the ID before creating a new MpesaTransaction

func (MpesaTransaction) TableName added in v1.4.1

func (MpesaTransaction) TableName() string

TableName specifies the table name for MpesaTransaction model

type MpesaTransactionConfirmVia added in v1.4.1

type MpesaTransactionConfirmVia string

MpesaTransactionConfirmVia is how an observation was independently verified.

const (
	MpesaConfirmViaSTKQuery          MpesaTransactionConfirmVia = "stk_query"
	MpesaConfirmViaPull              MpesaTransactionConfirmVia = "pull"
	MpesaConfirmViaTransactionStatus MpesaTransactionConfirmVia = "transaction_status"
)

The confirmation paths.

type MpesaTransactionReversal added in v1.4.1

type MpesaTransactionReversal string

MpesaTransactionReversal is the state of a reversal for this transaction.

const (
	MpesaReversalNone     MpesaTransactionReversal = "none"
	MpesaReversalProposed MpesaTransactionReversal = "proposed"
	MpesaReversalApproved MpesaTransactionReversal = "approved"
	MpesaReversalSent     MpesaTransactionReversal = "sent"
	MpesaReversalComplete MpesaTransactionReversal = "complete"
	MpesaReversalFailed   MpesaTransactionReversal = "failed"
)

The reversal states.

type MpesaTransactionSource added in v1.4.1

type MpesaTransactionSource string

MpesaTransactionSource is where an inbound notification came from. It decides which confirmation path can verify it.

const (
	MpesaSourceSTKCallback     MpesaTransactionSource = "stk_callback"
	MpesaSourceC2BConfirmation MpesaTransactionSource = "c2b_confirmation"
	MpesaSourcePull            MpesaTransactionSource = "pull"
	MpesaSourceStatusResult    MpesaTransactionSource = "status_result"
	MpesaSourceSTKQuery        MpesaTransactionSource = "stk_query"
)

The inbound sources.

type SecurityQuestion

type SecurityQuestion struct {
	ID         string    `json:"id" gorm:"type:uuid;primaryKey"`
	UserID     string    `json:"user_id" gorm:"type:uuid;not null;index"`
	QuestionID int       `json:"question_id" gorm:"not null"`
	AnswerHash string    `json:"-" gorm:"type:varchar(72);not null"`
	CreatedAt  time.Time `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt  time.Time `json:"updated_at" gorm:"autoUpdateTime;not null"`

	User User `gorm:"foreignKey:UserID"`
}

SecurityQuestion stores a hashed answer to a predefined security question for a given user. Each user may have multiple security questions, identified by QuestionID (an integer index into a predefined question list). The combination of (UserID, QuestionID) is unique.

func (*SecurityQuestion) BeforeCreate

func (sq *SecurityQuestion) BeforeCreate(tx *gorm.DB) error

BeforeCreate sets a UUIDv7 primary key before inserting a new row.

func (SecurityQuestion) TableName

func (SecurityQuestion) TableName() string

TableName specifies the table name for the SecurityQuestion model.

type Transaction

type Transaction struct {
	ID               string    `json:"id" gorm:"type:uuid;primaryKey"`
	UserID           *string   `json:"user_id,omitempty" gorm:"type:uuid;index"`
	AccountID        *string   `json:"account_id,omitempty" gorm:"type:uuid;index"`
	LoanID           *string   `json:"loan_id,omitempty" gorm:"type:uuid;index"`
	TxType           string    `json:"tx_type" gorm:"type:varchar(50);not null;index"`
	Amount           int64     `json:"amount" gorm:"type:bigint;not null"`
	Asset            string    `json:"asset" gorm:"type:varchar(20);not null;index"`
	StellarTxHash    *string   `json:"stellar_tx_hash,omitempty" gorm:"type:varchar(64);uniqueIndex"`
	StellarLedger    *int64    `json:"stellar_ledger,omitempty" gorm:"type:bigint"`
	ContractID       *string   `json:"contract_id,omitempty" gorm:"type:varchar(56);index"`
	ContractFunction *string   `json:"contract_function,omitempty" gorm:"type:varchar(100)"`
	ExternalID       *string   `json:"external_id,omitempty" gorm:"type:varchar(100);index"`
	ExternalProvider *string   `json:"external_provider,omitempty" gorm:"type:varchar(50);index"`
	ExternalStatus   *string   `json:"external_status,omitempty" gorm:"type:varchar(20)"`
	Description      *string   `json:"description,omitempty" gorm:"type:text"`
	Metadata         *string   `json:"metadata,omitempty" gorm:"type:jsonb"`
	Status           string    `json:"status" gorm:"type:varchar(20);not null;default:'pending';index"`
	CreatedAt        time.Time `json:"created_at" gorm:"autoCreateTime;not null;index"`
	UpdatedAt        time.Time `json:"updated_at" gorm:"autoUpdateTime;not null"`

	User    *User    `gorm:"foreignKey:UserID"`
	Account *Account `gorm:"foreignKey:AccountID"`
}

Transaction represents a blockchain or off-chain transaction

func (*Transaction) BeforeCreate

func (transaction *Transaction) BeforeCreate(tx *gorm.DB) error

BeforeCreate sets the ID before creating a new transaction

func (Transaction) TableName

func (Transaction) TableName() string

TableName specifies the table name for Transaction model

type User

type User struct {
	ID                string     `json:"id" gorm:"type:uuid;primaryKey"`
	MobileNumber      string     `json:"mobile_number" gorm:"type:varchar(20);uniqueIndex;not null"`
	CountryCode       string     `json:"country_code" gorm:"type:varchar(5);not null;default:'KE'"`
	MobileNetworkCode string     `json:"mobile_network_code" gorm:"type:varchar(6);not null;default:'99999'"`
	MomoNetworkCode   string     `json:"momo_network_code" gorm:"type:varchar(20);not null;default:'SANDBOX'"`
	MomoNetworkName   string     `json:"momo_network_name" gorm:"type:varchar(20);not null;default:'Sandbox Network'"`
	TelcoName         string     `json:"telco_name" gorm:"type:varchar(20);not null;default:'Athena'"`
	FullName          *string    `json:"full_name,omitempty" gorm:"type:varchar(255)"`
	BirthDate         *Date      `json:"birth_date,omitempty" gorm:"type:date"`
	Address           *string    `json:"address,omitempty" gorm:"type:varchar(255)"`
	City              *string    `json:"city,omitempty" gorm:"type:varchar(255)"`
	PostalCode        *string    `json:"postal_code,omitempty" gorm:"type:varchar(20)"`
	NationalID        *string    `json:"national_id,omitempty" gorm:"type:varchar(50);uniqueIndex"`
	KYCStatus         string     `json:"kyc_status" gorm:"type:varchar(20);not null;default:'pending'"`
	KYCVerifiedAt     *time.Time `json:"kyc_verified_at,omitempty" gorm:"type:timestamp"`
	PinHash           *string    `json:"-" gorm:"type:varchar(72)"`
	PinAttempts       int        `json:"-" gorm:"not null;default:0"`
	PinLockedUntil    *time.Time `json:"-" gorm:"type:timestamp"`
	PinSetAt          *time.Time `json:"-" gorm:"type:timestamp"`

	PreferredLanguage string     `json:"preferred_language" gorm:"type:varchar(10);not null;default:'en'"`
	Status            string     `json:"status" gorm:"type:varchar(20);not null;default:'active'"`
	Role              string     `json:"role" gorm:"type:varchar(20);not null;default:'user'"`
	CreatedAt         time.Time  `json:"created_at" gorm:"autoCreateTime;not null"`
	UpdatedAt         time.Time  `json:"updated_at" gorm:"autoUpdateTime;not null"`
	DeletedAt         *time.Time `json:"deleted_at,omitempty" gorm:"index"`
}

User represents a system user

func (*User) BeforeCreate

func (user *User) BeforeCreate(tx *gorm.DB) error

BeforeCreate sets the ID before creating a new user

func (User) TableName

func (User) TableName() string

TableName specifies the table name for User model

Jump to

Keyboard shortcuts

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