package wallet import ( "context" "hyapp/pkg/appcode" "hyapp/pkg/xerr" "hyapp/services/wallet-service/internal/domain/ledger" "strings" ) // AdminCreditCoinSellerStock 只调整 COIN_SELLER_COIN 库存;USDT 进货、USDT 冲回和金币补偿使用不同账务类型。 func (s *Service) AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) { if command.CommandID == "" || command.SellerUserID <= 0 || command.OperatorUserID <= 0 { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock command is incomplete") } if command.SellerCountryID <= 0 || command.SellerRegionID <= 0 { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "seller country and region are required") } if len(command.CommandID) > 128 { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "command_id is too long") } if command.CoinAmount <= 0 { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockAmountInvalid, "coin_amount must be positive") } command.StockType = ledger.NormalizeCoinSellerStockType(command.StockType) if command.StockType == "" { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockTypeInvalid, "stock_type is invalid") } command.PaidCurrencyCode = strings.ToUpper(strings.TrimSpace(command.PaidCurrencyCode)) command.PaymentRef = strings.TrimSpace(command.PaymentRef) command.EvidenceRef = strings.TrimSpace(command.EvidenceRef) command.Reason = strings.TrimSpace(command.Reason) if len(command.PaidCurrencyCode) > 8 || len(command.PaymentRef) > 128 || len(command.EvidenceRef) > 256 || len(command.Reason) > 512 { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock text fields are too long") } switch command.StockType { case ledger.StockTypeUSDTPurchase, ledger.StockTypeUSDTDeduction: if command.PaidCurrencyCode == "" { command.PaidCurrencyCode = ledger.PaidCurrencyUSDT } if command.PaidCurrencyCode != ledger.PaidCurrencyUSDT || command.PaidAmountMicro <= 0 { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockAmountInvalid, "usdt stock adjustment requires paid amount") } case ledger.StockTypeCoinCompensation: if command.PaidCurrencyCode != "" || command.PaidAmountMicro != 0 || command.PaymentRef != "" { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockAmountInvalid, "coin compensation must not include paid amount") } default: return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockTypeInvalid, "stock_type is invalid") } if s.repository == nil { return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.AdminCreditCoinSellerStock(ctx, command) } // TransferCoinFromSeller 执行币商专用金币转普通金币;身份和区域都由 gateway/user-service 校验并传入。 func (s *Service) TransferCoinFromSeller(ctx context.Context, command ledger.CoinSellerTransferCommand) (ledger.CoinSellerTransferReceipt, error) { if command.CommandID == "" || command.SellerUserID <= 0 || command.TargetUserID <= 0 || command.Amount <= 0 { return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller transfer command is incomplete") } if command.SellerRegionID <= 0 || command.TargetRegionID <= 0 { return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "seller and target region are required") } if command.TargetCountryID <= 0 { return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "target country is required") } if command.SellerRegionID != command.TargetRegionID { return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.Conflict, "seller and target region must match") } if strings.TrimSpace(command.Reason) == "" { return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.InvalidArgument, "reason is required") } if s.repository == nil { return ledger.CoinSellerTransferReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) command.Reason = strings.TrimSpace(command.Reason) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.TransferCoinFromSeller(ctx, command) } // ListCoinSellerSalaryExchangeRateTiers 返回指定区域的工资转币商比例区间;gateway 用它给 H5 展示预计到账金币。 func (s *Service) ListCoinSellerSalaryExchangeRateTiers(ctx context.Context, appCode string, regionID int64, includeDisabled bool) ([]ledger.CoinSellerSalaryExchangeRateTier, error) { if regionID <= 0 { return nil, xerr.New(xerr.InvalidArgument, "region_id is required") } if s.repository == nil { return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured") } appCode = appcode.Normalize(appCode) ctx = appcode.WithContext(ctx, appCode) return s.repository.ListCoinSellerSalaryExchangeRateTiers(ctx, appCode, regionID, includeDisabled) } // ExchangeSalaryToCoin 校验工资兑换命令;身份归属由 gateway 校验,wallet 只接受明确的工资资产和正向美分金额。 func (s *Service) ExchangeSalaryToCoin(ctx context.Context, command ledger.SalaryExchangeCommand) (ledger.SalaryExchangeReceipt, error) { if command.CommandID == "" || command.UserID <= 0 || command.SalaryUSDMinor <= 0 { return ledger.SalaryExchangeReceipt{}, xerr.New(xerr.InvalidArgument, "salary exchange command is incomplete") } command.SalaryAssetType = strings.ToUpper(strings.TrimSpace(command.SalaryAssetType)) if !ledger.ValidSalaryAssetType(command.SalaryAssetType) { return ledger.SalaryExchangeReceipt{}, xerr.New(xerr.InvalidArgument, "salary_asset_type is invalid") } command.Reason = strings.TrimSpace(command.Reason) if command.Reason == "" { command.Reason = "salary exchange" } if len(command.CommandID) > 128 || len(command.Reason) > 512 { return ledger.SalaryExchangeReceipt{}, xerr.New(xerr.InvalidArgument, "salary exchange text fields are too long") } if s.repository == nil { return ledger.SalaryExchangeReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.ExchangeSalaryToCoin(ctx, command) } // TransferSalaryToCoinSeller 校验工资转币商命令;比例区间命中和双边入账由 repository 在同一事务内完成。 func (s *Service) TransferSalaryToCoinSeller(ctx context.Context, command ledger.SalaryTransferToCoinSellerCommand) (ledger.SalaryTransferToCoinSellerReceipt, error) { if command.CommandID == "" || command.SourceUserID <= 0 || command.SellerUserID <= 0 || command.SalaryUSDMinor <= 0 { return ledger.SalaryTransferToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "salary transfer command is incomplete") } if command.RegionID <= 0 { return ledger.SalaryTransferToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "region_id is required") } command.SalaryAssetType = strings.ToUpper(strings.TrimSpace(command.SalaryAssetType)) if !ledger.ValidSalaryAssetType(command.SalaryAssetType) { return ledger.SalaryTransferToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "salary_asset_type is invalid") } command.Reason = strings.TrimSpace(command.Reason) if command.Reason == "" { command.Reason = "salary transfer" } if len(command.CommandID) > 128 || len(command.Reason) > 512 { return ledger.SalaryTransferToCoinSellerReceipt{}, xerr.New(xerr.InvalidArgument, "salary transfer text fields are too long") } if s.repository == nil { return ledger.SalaryTransferToCoinSellerReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") } command.AppCode = appcode.Normalize(command.AppCode) ctx = appcode.WithContext(ctx, command.AppCode) return s.repository.TransferSalaryToCoinSeller(ctx, command) }