mirror of
https://github.com/luxfi/broker.git
synced 2026-09-24 00:25:18 +00:00
169 lines
5.3 KiB
Go
169 lines
5.3 KiB
Go
package funding
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/hanzoai/commerce/models/types/currency"
|
|
"github.com/hanzoai/commerce/payment/processor"
|
|
)
|
|
|
|
// Service handles deposit and withdrawal operations via payment processors.
|
|
type Service struct {
|
|
registry *processor.Registry
|
|
}
|
|
|
|
// New creates a funding service using the global processor registry.
|
|
func New() *Service {
|
|
return &Service{registry: processor.Global()}
|
|
}
|
|
|
|
// NewWithRegistry creates a funding service with a custom registry.
|
|
func NewWithRegistry(r *processor.Registry) *Service {
|
|
return &Service{registry: r}
|
|
}
|
|
|
|
// DepositRequest represents a request to deposit funds into a trading account.
|
|
type DepositRequest struct {
|
|
AccountID string `json:"account_id"`
|
|
Provider string `json:"provider"`
|
|
Amount int64 `json:"amount"` // cents
|
|
Currency string `json:"currency"` // e.g. "usd", "btc"
|
|
PaymentMethod string `json:"payment_method"` // "card", "bank_transfer", "crypto"
|
|
Token string `json:"token,omitempty"` // payment method nonce/token (card)
|
|
TxHash string `json:"tx_hash,omitempty"` // on-chain tx hash (crypto)
|
|
Chain string `json:"chain,omitempty"` // blockchain network (crypto)
|
|
Address string `json:"address,omitempty"` // deposit address (crypto)
|
|
}
|
|
|
|
// WithdrawRequest represents a request to withdraw funds from a trading account.
|
|
type WithdrawRequest struct {
|
|
AccountID string `json:"account_id"`
|
|
Provider string `json:"provider"`
|
|
Amount int64 `json:"amount"` // cents
|
|
Currency string `json:"currency"`
|
|
PaymentMethod string `json:"payment_method"` // "bank_transfer", "crypto"
|
|
RelationshipID string `json:"relationship_id,omitempty"`
|
|
DestAddress string `json:"dest_address,omitempty"`
|
|
Chain string `json:"chain,omitempty"`
|
|
}
|
|
|
|
// Result is returned from deposit/withdraw operations.
|
|
type Result struct {
|
|
ID string `json:"id"`
|
|
Status string `json:"status"` // "pending", "completed", "failed", "authorized"
|
|
Amount int64 `json:"amount"`
|
|
Currency string `json:"currency"`
|
|
PaymentMethod string `json:"payment_method"`
|
|
ProcessorRef string `json:"processor_ref,omitempty"`
|
|
CreatedAt string `json:"created_at"`
|
|
}
|
|
|
|
// Deposit processes a deposit using the appropriate payment processor.
|
|
func (s *Service) Deposit(ctx context.Context, req *DepositRequest) (*Result, error) {
|
|
cur := currency.Type(req.Currency)
|
|
isCrypto := processor.IsCryptoCurrency(cur)
|
|
|
|
payReq := processor.PaymentRequest{
|
|
Amount: currency.Cents(req.Amount),
|
|
Currency: cur,
|
|
Metadata: map[string]interface{}{
|
|
"account_id": req.AccountID,
|
|
"provider": req.Provider,
|
|
"type": "deposit",
|
|
},
|
|
}
|
|
|
|
if req.Token != "" {
|
|
payReq.Token = req.Token
|
|
}
|
|
if isCrypto {
|
|
payReq.Address = req.Address
|
|
payReq.Chain = req.Chain
|
|
}
|
|
|
|
proc, err := s.registry.SelectProcessor(ctx, payReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no processor available for %s: %w", req.Currency, err)
|
|
}
|
|
|
|
result, err := proc.Charge(ctx, payReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("deposit charge failed: %w", err)
|
|
}
|
|
|
|
return &Result{
|
|
ID: result.TransactionID,
|
|
Status: result.Status,
|
|
Amount: int64(payReq.Amount),
|
|
Currency: req.Currency,
|
|
PaymentMethod: req.PaymentMethod,
|
|
ProcessorRef: result.ProcessorRef,
|
|
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
|
}, nil
|
|
}
|
|
|
|
// Withdraw processes a withdrawal using the appropriate payment processor.
|
|
func (s *Service) Withdraw(ctx context.Context, req *WithdrawRequest) (*Result, error) {
|
|
cur := currency.Type(req.Currency)
|
|
|
|
payReq := processor.PaymentRequest{
|
|
Amount: currency.Cents(req.Amount),
|
|
Currency: cur,
|
|
Metadata: map[string]interface{}{
|
|
"account_id": req.AccountID,
|
|
"provider": req.Provider,
|
|
"type": "withdrawal",
|
|
"relationship_id": req.RelationshipID,
|
|
"dest_address": req.DestAddress,
|
|
"chain": req.Chain,
|
|
},
|
|
}
|
|
|
|
if req.DestAddress != "" {
|
|
payReq.Address = req.DestAddress
|
|
payReq.Chain = req.Chain
|
|
}
|
|
|
|
proc, err := s.registry.SelectProcessor(ctx, payReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no processor available for %s: %w", req.Currency, err)
|
|
}
|
|
|
|
result, err := proc.Charge(ctx, payReq)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("withdrawal failed: %w", err)
|
|
}
|
|
|
|
return &Result{
|
|
ID: result.TransactionID,
|
|
Status: result.Status,
|
|
Amount: int64(payReq.Amount),
|
|
Currency: req.Currency,
|
|
PaymentMethod: req.PaymentMethod,
|
|
ProcessorRef: result.ProcessorRef,
|
|
CreatedAt: time.Now().UTC().Format(time.RFC3339),
|
|
}, nil
|
|
}
|
|
|
|
// ValidateWebhook validates and parses an incoming payment webhook.
|
|
func (s *Service) ValidateWebhook(ctx context.Context, processorName string, payload []byte, signature string) (*processor.WebhookEvent, error) {
|
|
pt := processor.ProcessorType(processorName)
|
|
proc, err := s.registry.Get(pt)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unknown processor %q: %w", processorName, err)
|
|
}
|
|
return proc.ValidateWebhook(ctx, payload, signature)
|
|
}
|
|
|
|
// ListProcessors returns available processor type names.
|
|
func (s *Service) ListProcessors(ctx context.Context) []string {
|
|
procs := s.registry.Available(ctx)
|
|
names := make([]string, len(procs))
|
|
for i, p := range procs {
|
|
names[i] = string(p.Type())
|
|
}
|
|
return names
|
|
}
|