webhook

package
v1.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: AGPL-3.0 Imports: 6 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).

Flow:

  1. Fetch loans in DisbursementRefundPending status from local DB
  2. For each, call YC API to check current status
  3. On "refunded" to update to DisbursementRefundReceived, attempt fiat failover
  4. On "refund_failed" to update to DisbursementFailed, alert ops
  5. On "pending_refund" / "refund_processing" to skip, poll again next cycle

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