mirror of
https://github.com/hanzo-fi/payments.git
synced 2026-10-30 20:31:20 +00:00
107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
package mappers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/hanzo-fi/go-libs/v5/pkg/types/pointer"
|
|
"github.com/hanzo-fi/payments/ee/plugins/bitstamp/client"
|
|
"github.com/hanzo-fi/payments/pkg/domain/models"
|
|
)
|
|
|
|
// ConversionMapResult — Conversion != nil → emit; Skip → ignore;
|
|
// DerivativesRow → orchestrator Errorf-skip (spot-only).
|
|
type ConversionMapResult struct {
|
|
Conversion *models.PSPConversion
|
|
Skip bool
|
|
DerivativesRow bool
|
|
}
|
|
|
|
// UserTransactionToPSPConversion detects type-36 (instant buy/sell)
|
|
// rows with exactly one negative + one positive non-zero
|
|
// known-currency amount. See MAPPINGS §4.5.
|
|
func UserTransactionToPSPConversion(currencies map[string]int, tx client.UserTransaction) (ConversionMapResult, error) {
|
|
if tx.HasDerivativesMarker() {
|
|
return ConversionMapResult{Skip: true, DerivativesRow: true}, nil
|
|
}
|
|
if !IsConversionType(tx.Type) {
|
|
return ConversionMapResult{Skip: true}, nil
|
|
}
|
|
|
|
source, destination, ok, err := ResolveTwoAssetConversion(currencies, tx.CurrencyAmounts)
|
|
if err != nil {
|
|
return ConversionMapResult{}, fmt.Errorf("resolve conversion legs for tx %d: %w", tx.ID, err)
|
|
}
|
|
if !ok {
|
|
return ConversionMapResult{Skip: true}, nil
|
|
}
|
|
|
|
createdAt, err := ParseBitstampTime(tx.Datetime)
|
|
if err != nil {
|
|
return ConversionMapResult{}, fmt.Errorf("conversion tx %d: %w", tx.ID, err)
|
|
}
|
|
|
|
raw, err := json.Marshal(tx)
|
|
if err != nil {
|
|
return ConversionMapResult{}, fmt.Errorf("marshal raw for tx %d: %w", tx.ID, err)
|
|
}
|
|
|
|
pairKey, pairRate := pickPairRate(tx.PairRates, source.Symbol, destination.Symbol)
|
|
currencyPair := pairKey
|
|
if currencyPair == "" {
|
|
// Synthesise stable currency_pair when Bitstamp omits the rate key.
|
|
currencyPair = strings.ToLower(source.Symbol) + "_" + strings.ToLower(destination.Symbol)
|
|
}
|
|
|
|
conv := &models.PSPConversion{
|
|
Reference: strconv.FormatInt(tx.ID, 10),
|
|
CreatedAt: createdAt,
|
|
SourceAsset: source.Asset,
|
|
DestinationAsset: destination.Asset,
|
|
SourceAmount: source.Amount,
|
|
DestinationAmount: destination.Amount,
|
|
Status: models.CONVERSION_STATUS_COMPLETED,
|
|
SourceAccountReference: pointer.For(source.Symbol),
|
|
DestinationAccountReference: pointer.For(destination.Symbol),
|
|
Metadata: ConversionMetadata(tx, currencyPair, pairRate),
|
|
Raw: raw,
|
|
}
|
|
|
|
// Fee is in the quote currency. Identify the quote leg from the pair
|
|
// key (e.g. "usdc_eur" → quote = "eur"). For a buy the quote is the
|
|
// source; for a sell it is the destination. Fall back to destination
|
|
// when the pair key is unavailable.
|
|
feeLeg := destination
|
|
if parts := strings.SplitN(pairKey, "_", 2); len(parts) == 2 {
|
|
if strings.EqualFold(source.Symbol, parts[1]) {
|
|
feeLeg = source
|
|
}
|
|
}
|
|
if !IsZeroAmount(tx.Fee) {
|
|
fee, ferr := ParseDecimalAmount(AbsAmount(tx.Fee), feeLeg.Precision)
|
|
if ferr != nil {
|
|
return ConversionMapResult{}, fmt.Errorf("conversion tx %d fee: %w", tx.ID, ferr)
|
|
}
|
|
conv.Fee = fee
|
|
feeAsset := feeLeg.Asset
|
|
conv.FeeAsset = &feeAsset
|
|
}
|
|
|
|
return ConversionMapResult{Conversion: conv}, nil
|
|
}
|
|
|
|
// pickPairRate tries both <src>_<dst> and <dst>_<src> — Bitstamp
|
|
// keys the rate on the market pair, not the trade direction.
|
|
func pickPairRate(pairs map[string]string, source, destination string) (key, rate string) {
|
|
src := strings.ToLower(source)
|
|
dst := strings.ToLower(destination)
|
|
for _, candidate := range []string{src + "_" + dst, dst + "_" + src} {
|
|
if v, ok := pairs[candidate]; ok && v != "" {
|
|
return candidate, v
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|