webhook

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

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

type RefundPollerConfig struct {
	PollInterval time.Duration // How often to poll (default: 30s)
}

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.

Jump to

Keyboard shortcuts

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