Documentation
¶
Overview ¶
Package webhook reacts to off-ramp settlement outcomes. It has two halves: a service that processes inbound payment-provider webhook events, and a poller that chases disbursements stuck awaiting a refund.
Both depend only on consumer-defined interfaces — DisbursementUpdater, PaymentLookup, TransactionRecorder, AlertService — which the loan/disbursement service implements. That keeps this package free of any dependency on the lending module while still driving loan state.
Event service ¶
Service (webhook_service.go) implements WebhookEventHandler. The HTTP layer verifies a webhook's signature and hands the event here; the service maps it to a disbursement status change and the side effects that follow — notifying the user, recording the final financials, and repaying the vault on completion. Failures are branched by settlement method: a FAILED event on a direct settlement becomes refund-pending, while one on a fiat settlement is terminal, a distinction the service resolves with IsDirectSettlement rather than relying on the provider to flag it.
Refund poller ¶
RefundPoller (refund_poller.go) runs in the background on an interval, fetching disbursements left in the refund-pending state — direct settlements whose USDC was returned to the treasury — and attempting a fiat failover so the borrower is still paid. It marks the loan's settlement method so the eventual completion event takes the vault-repay path, and alerts ops when a case needs a human.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AlertService ¶
type AlertService interface {
// AlertOps sends an alert to the operations team.
AlertOps(subject string, message string) error
}
AlertService is the interface for sending operational alerts.
type CompletionFinancials ¶ added in v1.0.0
type CompletionFinancials struct {
ConvertedAmountLocal float64
ServiceFeeAmountUSD float64
ServiceFeeAmountLocal float64
PartnerFeeAmountUSD float64
PartnerFeeAmountLocal float64
}
CompletionFinancials carries the final amounts from a completed YellowCard payment, looked up at completion time.
type DisbursementUpdater ¶
type DisbursementUpdater interface {
// UpdateDisbursementStatus updates the disbursement status for a loan identified by sequenceID.
UpdateDisbursementStatus(sequenceID string, status string) error
// NotifyDisbursementComplete sends a notification (SMS) to the user that their disbursement completed.
NotifyDisbursementComplete(sequenceID string) error
// RecordDisbursementCompletion persists the final financials of a completed
// payment: the delivered local amount and the service/partner fees.
RecordDisbursementCompletion(sequenceID string, fin CompletionFinancials) error
// NotifyDisbursementFailed sends a notification (SMS) to the user that their disbursement failed.
NotifyDisbursementFailed(sequenceID string) error
// RepayVault returns borrowed USDC from treasury to the vault pool for the
// loan identified by sequenceID. No-op if already repaid.
RepayVault(sequenceID string) error
// SetSettlementMethod updates the loan's settlement_method field. Called
// by RefundPoller when a direct-mode disbursement is failed over to fiat
// so the eventual DisbursementComplete handler correctly triggers the
// vault repay branch.
SetSettlementMethod(sequenceID string, method string) error
// IsDirectSettlement reports whether the loan identified by sequenceID
// was disbursed via direct settlement. Lets the webhook service branch
// FAILED events into refund-pending (direct) vs terminal-failed (fiat)
// without YC having to send a directSettlement flag on every webhook.
IsDirectSettlement(sequenceID string) (bool, error)
}
DisbursementUpdater is the interface for updating loan disbursement status. Implemented by the loan/disbursement service.
type PaymentLookup ¶ added in v1.0.0
type PaymentLookup interface {
LookupPayment(ctx context.Context, paymentID string) (*yellowcard.PaymentDetails, error)
}
PaymentLookup fetches final payment details (amounts, fees) at completion.
type RefundPendingFetcher ¶
type RefundPendingFetcher interface {
// GetRefundPendingDisbursements returns (sequenceID, paymentID) pairs for all
// disbursements in DisbursementRefundPending status.
GetRefundPendingDisbursements() ([]RefundPendingRecord, error)
}
RefundPendingFetcher retrieves loans that are awaiting crypto refund from YellowCard.
type RefundPendingRecord ¶
type RefundPendingRecord struct {
SequenceID string // YC sequenceId / idempotency key
PaymentID string // YC payment ID
LoanID string // Loan ID for tracking
UserID string // User ID for tracking
RecipientName string // Recipient name for fiat disbursement
AmountUSD float64 // Amount in USD
AmountStroops int64 // Amount in stroops (USDC * 10^7)
RampFiatAmount int64 // Original off-ramp fiat amount in cents (local currency)
RampFiatCurrency string // Original off-ramp fiat currency (e.g. "KES")
DestinationPhone string // Recipient phone number
CountryCode string // ISO country code
NetworkCode string // MoMo network code
NetworkName string // MoMo network name
}
RefundPendingRecord identifies a disbursement awaiting refund, including the data needed to attempt a fiat failover.
type RefundPoller ¶
type RefundPoller struct {
// contains filtered or unexported fields
}
RefundPoller periodically checks YellowCard for refund status on disbursements that failed after USDC was sent (direct settlement F3 failover).
func NewRefundPoller ¶
func NewRefundPoller( ycAdapter *yellowcard.YellowcardAdapter, offRamp offramp.Provider, fetcher RefundPendingFetcher, disbursement DisbursementUpdater, alerts AlertService, transactions TransactionRecorder, config RefundPollerConfig, ) *RefundPoller
NewRefundPoller creates a new RefundPoller.
func (*RefundPoller) Start ¶
func (p *RefundPoller) Start(ctx context.Context)
Start runs the RefundPoller in a background goroutine. It polls until the context is cancelled (graceful shutdown).
type RefundPollerConfig ¶
RefundPollerConfig configures the refund polling behavior.
func DefaultRefundPollerConfig ¶
func DefaultRefundPollerConfig() RefundPollerConfig
DefaultRefundPollerConfig returns sensible defaults.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service processes incoming payment provider webhook events.
func NewService ¶
func NewService(disbursements DisbursementUpdater, alerts AlertService, transactions TransactionRecorder, payments PaymentLookup) *Service
NewService creates a new webhook processing service.
func (*Service) ProcessYellowCardEvent ¶
func (s *Service) ProcessYellowCardEvent(event yellowcard.WebhookEvent) error
ProcessYellowCardEvent handles a single YellowCard webhook event by mapping it to the appropriate disbursement status update and side effects.
Event to Action mapping:
DISBURSEMENT.COMPLETE / PAYMENT.COMPLETE / SEND.COMPLETE to DisbursementComplete + notify user DISBURSEMENT.FAILED / PAYMENT.FAILED / SEND.FAILED to if direct: DisbursementRefundPending; if fiat: DisbursementFailed + alert ops PENDING_LIQUIDITY to alert ops (YC balance low, auto-retries for 2hrs) REFUNDED to DisbursementRefundReceived (RefundPoller handles fiat failover) REFUND_FAILED to DisbursementFailed + alert ops EXPIRED / CANCELLED to treat as FAILED PROCESSING / PENDING / PROCESS to DisbursementProcessing
type TransactionRecorder ¶
type TransactionRecorder interface {
// UpdateOffRampTransaction syncs a loan's off-ramp row with the provider's
// reported status. Keyed by loan rather than by external ID, which no longer
// identifies a single row now that every leg of an anchor transaction shares
// the provider's request ID.
UpdateOffRampTransaction(ctx context.Context, loanID string, status string, externalStatus string) error
// RecordFiatFailover records a fiat failover transaction after a direct settlement refund.
RecordFiatFailover(ctx context.Context, rec RefundPendingRecord, newRequestID string) error
}
TransactionRecorder records and updates transaction records for disbursement events.
type WebhookEventHandler ¶
type WebhookEventHandler interface {
// ProcessYellowCardEvent handles a single webhook event, mapping it to the
// appropriate disbursement status update and triggering any side effects.
ProcessYellowCardEvent(event yellowcard.WebhookEvent) error
}
WebhookEventHandler processes incoming YellowCard webhook events.