Documentation
¶
Overview ¶
Package offramp defines the off-ramp Provider contract that every provider in the platform satisfies.
Provider is the single mandatory interface (ID + Initiate). Optional capabilities — StatusReader, Quoter, Directory, MobileMoneyDirectory, BalanceReporter — are separate small interfaces; consumers type-assert for what they need and providers implement only what they genuinely support.
Request and Result carry only cross-provider fields. Provider-specific extras travel on typed payloads via the ProviderOptions / ProviderPayload marker interfaces; the concrete types live in each provider's own package.
Registry resolves a Request to a concrete Provider, first by inspecting req.Options.ProviderID(), otherwise by aliasing req.PayoutMethod to a registered ProviderID. NoOpProvider in noop.go is a stand-in for tests.
Index ¶
- Constants
- func Fraction(f float64) *float64
- type BalanceReporter
- type Directory
- type ExchangeRate
- type MobileMoneyDirectory
- type MobileMoneyNetwork
- type NoOpProvider
- type Provider
- type ProviderID
- type ProviderInfo
- type ProviderOptions
- type ProviderPayload
- type ProviderRef
- type QuoteRequest
- type Quoter
- type RateBuffer
- type Registry
- type Request
- type Result
- type Status
- type StatusReader
- type TreasuryTransfer
Constants ¶
const ( PayoutMethodMobileMoney = "mobile_money" PayoutMethodCashPickup = "cash_pickup" )
PayoutMethod selects which provider handles a request when multiple are wired in via the registry. Empty string is treated as PayoutMethodMobileMoney.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type BalanceReporter ¶
BalanceReporter reports a pre-funded balance held with the provider. Not implemented by providers that settle per-transaction (e.g. MoneyGram).
type Directory ¶
type Directory interface {
SupportedProviders(ctx context.Context, countryCode string) ([]ProviderInfo, error)
}
Directory advertises which provider configurations are available for a given country (e.g. YC's per-channel info, MG's cash-pickup option).
type ExchangeRate ¶
type ExchangeRate struct {
FromCurrency string
ToCurrency string
SellRate float64
BuyRate float64
RateID string
Locale string
UpdatedAt time.Time
}
ExchangeRate is the canonical quote shape returned by Quoter.
type MobileMoneyDirectory ¶
type MobileMoneyDirectory interface {
Networks(ctx context.Context, countryCode string) ([]MobileMoneyNetwork, error)
}
MobileMoneyDirectory lists the MoMo operators available in a country. Only implemented by providers that actually operate on mobile-money rails.
type MobileMoneyNetwork ¶
MobileMoneyNetwork represents a MoMo operator.
type NoOpProvider ¶
type NoOpProvider struct{}
NoOpProvider is a minimal Provider stub for tests and bootstrap wiring. It returns a mock Result and nothing else — callers that need richer behaviour should depend on a real provider or a hand-rolled fake.
type Provider ¶
type Provider interface {
ID() ProviderID
Initiate(ctx context.Context, req Request) (*Result, error)
}
Provider is the single mandatory capability — every off-ramp must register and initiate. Optional behaviour is split into the other capability interfaces below; consumers type-assert for what they need.
type ProviderID ¶
type ProviderID string
ProviderID identifies a concrete off-ramp implementation in the registry.
const ( ProviderYellowCard ProviderID = "yellowcard" ProviderMoneyGram ProviderID = "moneygram" )
Known provider IDs. New providers should add their constant here.
type ProviderInfo ¶
type ProviderInfo struct {
ID string
Name string
SupportedMethods []string
MinAmount float64
MaxAmount float64
Currency string
Status string
FeeUSD float64
FeeLocal float64
EstimatedSettlementTime int
}
ProviderInfo describes an available off-ramp provider for a country.
type ProviderOptions ¶
type ProviderOptions interface {
ProviderID() ProviderID
}
ProviderOptions is the marker interface implemented by per-provider request extras (e.g. yellowcard.Options.SettlementMethod, moneygram.Options.BirthDate). Callers attach a concrete implementation to Request.Options; the receiving adapter type-asserts to its own concrete type.
type ProviderPayload ¶
type ProviderPayload interface {
ProviderID() ProviderID
}
ProviderPayload is the marker interface implemented by per-provider result extras (e.g. yellowcard.DirectSettlementPayload, moneygram.CashPickupPayload). Adapters set Result.Provider to a concrete implementation; consumers type- assert to read provider-specific output.
type ProviderRef ¶
type ProviderRef struct {
ID string
Provider ProviderID
Extra map[string]any
}
ProviderRef identifies an in-flight transaction enough to look it up. Extra is provider-scoped; document the expected keys in each provider's adapter so callers know what to pass.
type QuoteRequest ¶
type QuoteRequest struct {
Currency string
}
QuoteRequest carries the inputs a provider needs to quote a corridor. Future fields (amount, originating country, service option) can be added without breaking the interface.
type Quoter ¶
type Quoter interface {
Quote(ctx context.Context, q QuoteRequest) (*ExchangeRate, error)
}
Quoter exposes the provider's view of the FX rate for a corridor. The quote response may be cached upstream; the orchestration of multi-source quoting lives outside this interface.
type RateBuffer ¶
type RateBuffer struct {
// contains filtered or unexported fields
}
RateBuffer is a fractional safety margin deducted from a quoted FX rate to hedge drift between the moment a borrower is quoted and the moment the off-ramp settles.
The margin belongs to the rate's *source*, not to the caller: a rate from the anchor that will itself lock it needs less headroom than a proxy rate from another provider. Callers therefore keep their own values and share only this mechanism.
func NewRateBuffer ¶
func NewRateBuffer(pct *float64, def float64) RateBuffer
NewRateBuffer resolves a configured fraction against a default.
pct is a pointer because zero is a meaningful setting — quote raw, no margin — and has to stay distinguishable from "not configured", which takes def instead.
func (RateBuffer) Apply ¶
func (b RateBuffer) Apply(rate float64) float64
Apply returns rate reduced by the margin, clamped at zero.
Reducing the rate is the conservative direction: it promises the borrower fewer local units per USD than the source quoted, so the settled rate is almost always in their favour. Slippage against them is what this hedges.
func (RateBuffer) Pct ¶
func (b RateBuffer) Pct() float64
Pct returns the resolved fraction, where 0.01 is 1 %. Zero means rates are quoted raw. Persist it alongside the rate so drift is auditable after the fact.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps ProviderIDs to Provider implementations and resolves an incoming Request to the right provider. The resolution order is:
- If Request.Options is set, use Options.ProviderID() — the caller has pinned a specific provider by attaching its options.
- Otherwise, look up Request.PayoutMethod in the alias table (empty PayoutMethod is treated as PayoutMethodMobileMoney).
A Registry is safe for concurrent reads after construction; concurrent Register / Alias calls are not synchronised — register everything during boot, then freeze.
func (*Registry) Alias ¶
func (r *Registry) Alias(payoutMethod string, id ProviderID) error
Alias maps a PayoutMethod string (e.g. PayoutMethodMobileMoney) to a registered ProviderID. The provider must already be registered.
func (*Registry) All ¶
All returns every registered Provider in unspecified order. Useful for fanning out menu rendering across providers (Directory capability).
func (*Registry) Get ¶
func (r *Registry) Get(id ProviderID) (Provider, bool)
Get returns the Provider for an explicit ID. Useful for callers that already know which provider they want (e.g. a poller bound to MG loans).
type Request ¶
type Request struct {
LoanID string
UserID string
RecipientName string
AmountUSD float64
AmountStroops int64
DestinationPhone string
CountryCode string
IdempotencyKey string
NetworkCode string
NetworkName string
// PayoutMethod is consulted by the registry to pick a provider when the
// caller doesn't pin one via Options. Empty defaults to mobile money.
PayoutMethod string
// Options carries per-provider extras (yellowcard.Options,
// moneygram.Options). nil is acceptable; each adapter documents its
// expectations for missing/wrong types.
Options ProviderOptions
}
Request contains the cross-provider data needed to initiate an off-ramp. Provider-specific extras live on Options.
type Result ¶
type Result struct {
RequestID string
SequenceID string
Status string
AmountUSD float64
AmountLocal float64
LocalCurrency string
ExchangeRate float64
Fee float64
FeeLocal float64
EstimatedTime int
CreatedAt time.Time
SettlementMethod string // tag for downstream persistence: "direct"|"fiat"|"cash_pickup"
// Provider is the typed payload returned by the adapter. Consumers
// type-assert to the concrete payload type owned by the provider package.
Provider ProviderPayload
}
Result is what Provider.Initiate returns. Cross-provider summary fields stay on the struct; provider-specific output lives in Provider.
type Status ¶
type Status struct {
RequestID string
SequenceID string
Status string
AmountLocal float64
LocalCurrency string
CompletedAt *time.Time
FailureReason *string
}
Status contains status information for an in-flight off-ramp.
type StatusReader ¶
type StatusReader interface {
Status(ctx context.Context, ref ProviderRef) (*Status, error)
}
StatusReader looks up an in-flight off-ramp by ProviderRef. ProviderRef carries the transaction ID plus provider-scoped extras (e.g. MoneyGram needs the child memo to authenticate the SEP-10 session).
type TreasuryTransfer ¶
type TreasuryTransfer interface {
SendUSDC(ctx context.Context, destination string, memo string, amount int64) (txHash string, err error)
CheckUSDCTrustline(ctx context.Context, address string) (hasTrustline bool, err error)
}
TreasuryTransfer abstracts sending USDC from the custodial treasury to an external Stellar address. Implemented by Stellar service adapters; injected into off-ramp providers that need to settle on-chain.