feat: expose coin seller transfer amounts
This commit is contained in:
parent
cb8cfd2167
commit
d7570d78c0
File diff suppressed because it is too large
Load Diff
@ -1485,6 +1485,8 @@ message WalletTransaction {
|
|||||||
int64 counterparty_user_id = 9;
|
int64 counterparty_user_id = 9;
|
||||||
string room_id = 10;
|
string room_id = 10;
|
||||||
int64 created_at_ms = 11;
|
int64 created_at_ms = 11;
|
||||||
|
int64 transfer_usd_minor = 12;
|
||||||
|
string transfer_currency_code = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ListWalletTransactionsRequest {
|
message ListWalletTransactionsRequest {
|
||||||
|
|||||||
@ -7986,14 +7986,16 @@ func TestListWalletTransactionsIncludesCounterpartyProfile(t *testing.T) {
|
|||||||
walletClient := &fakeWalletClient{transactionsResp: &walletv1.ListWalletTransactionsResponse{
|
walletClient := &fakeWalletClient{transactionsResp: &walletv1.ListWalletTransactionsResponse{
|
||||||
Total: 1,
|
Total: 1,
|
||||||
Transactions: []*walletv1.WalletTransaction{{
|
Transactions: []*walletv1.WalletTransaction{{
|
||||||
EntryId: 9,
|
EntryId: 9,
|
||||||
TransactionId: "wtx-seller-1",
|
TransactionId: "wtx-seller-1",
|
||||||
BizType: "coin_seller_transfer",
|
BizType: "coin_seller_transfer",
|
||||||
AssetType: "COIN_SELLER_COIN",
|
AssetType: "COIN_SELLER_COIN",
|
||||||
AvailableDelta: -10_000,
|
AvailableDelta: -10_000,
|
||||||
AvailableAfter: 1_011,
|
AvailableAfter: 1_011,
|
||||||
CounterpartyUserId: 9001,
|
CounterpartyUserId: 9001,
|
||||||
CreatedAtMs: 1_780_000_000_000,
|
CreatedAtMs: 1_780_000_000_000,
|
||||||
|
TransferUsdMinor: 1250,
|
||||||
|
TransferCurrencyCode: "USD",
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{
|
profileClient := &fakeUserProfileClient{usersByID: map[int64]*userv1.User{
|
||||||
@ -8034,7 +8036,7 @@ func TestListWalletTransactionsIncludesCounterpartyProfile(t *testing.T) {
|
|||||||
t.Fatalf("wallet transaction response mismatch: %+v", response)
|
t.Fatalf("wallet transaction response mismatch: %+v", response)
|
||||||
}
|
}
|
||||||
first, ok := items[0].(map[string]any)
|
first, ok := items[0].(map[string]any)
|
||||||
if !ok || first["counterparty_display_user_id"] != "163001" || first["counterparty_username"] != "Alice" || first["counterparty_avatar"] != "https://cdn.example/alice.png" {
|
if !ok || first["counterparty_display_user_id"] != "163001" || first["counterparty_username"] != "Alice" || first["counterparty_avatar"] != "https://cdn.example/alice.png" || first["transfer_usd_minor"] != float64(1250) || first["transfer_currency_code"] != "USD" {
|
||||||
t.Fatalf("counterparty profile fields mismatch: %+v", first)
|
t.Fatalf("counterparty profile fields mismatch: %+v", first)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,6 +65,8 @@ type walletTransactionData struct {
|
|||||||
CounterpartyAvatar string `json:"counterparty_avatar,omitempty"`
|
CounterpartyAvatar string `json:"counterparty_avatar,omitempty"`
|
||||||
RoomID string `json:"room_id"`
|
RoomID string `json:"room_id"`
|
||||||
CreatedAtMS int64 `json:"created_at_ms"`
|
CreatedAtMS int64 `json:"created_at_ms"`
|
||||||
|
TransferUSDMinor int64 `json:"transfer_usd_minor,omitempty"`
|
||||||
|
TransferCurrencyCode string `json:"transfer_currency_code,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type giftWallData struct {
|
type giftWallData struct {
|
||||||
@ -508,17 +510,19 @@ func walletTransactionFromProto(item *walletv1.WalletTransaction, profiles map[i
|
|||||||
return walletTransactionData{}
|
return walletTransactionData{}
|
||||||
}
|
}
|
||||||
data := walletTransactionData{
|
data := walletTransactionData{
|
||||||
EntryID: item.GetEntryId(),
|
EntryID: item.GetEntryId(),
|
||||||
TransactionID: item.GetTransactionId(),
|
TransactionID: item.GetTransactionId(),
|
||||||
BizType: item.GetBizType(),
|
BizType: item.GetBizType(),
|
||||||
AssetType: item.GetAssetType(),
|
AssetType: item.GetAssetType(),
|
||||||
AvailableDelta: item.GetAvailableDelta(),
|
AvailableDelta: item.GetAvailableDelta(),
|
||||||
FrozenDelta: item.GetFrozenDelta(),
|
FrozenDelta: item.GetFrozenDelta(),
|
||||||
AvailableAfter: item.GetAvailableAfter(),
|
AvailableAfter: item.GetAvailableAfter(),
|
||||||
FrozenAfter: item.GetFrozenAfter(),
|
FrozenAfter: item.GetFrozenAfter(),
|
||||||
CounterpartyUserID: item.GetCounterpartyUserId(),
|
CounterpartyUserID: item.GetCounterpartyUserId(),
|
||||||
RoomID: item.GetRoomId(),
|
RoomID: item.GetRoomId(),
|
||||||
CreatedAtMS: item.GetCreatedAtMs(),
|
CreatedAtMS: item.GetCreatedAtMs(),
|
||||||
|
TransferUSDMinor: item.GetTransferUsdMinor(),
|
||||||
|
TransferCurrencyCode: item.GetTransferCurrencyCode(),
|
||||||
}
|
}
|
||||||
if profile := profiles[item.GetCounterpartyUserId()]; profile != nil {
|
if profile := profiles[item.GetCounterpartyUserId()]; profile != nil {
|
||||||
data.CounterpartyDisplayUserID = profile.GetDisplayUserId()
|
data.CounterpartyDisplayUserID = profile.GetDisplayUserId()
|
||||||
|
|||||||
@ -129,17 +129,19 @@ type DiamondExchangeRule struct {
|
|||||||
|
|
||||||
// WalletTransaction 是钱包流水页按用户分录查询的投影。
|
// WalletTransaction 是钱包流水页按用户分录查询的投影。
|
||||||
type WalletTransaction struct {
|
type WalletTransaction struct {
|
||||||
EntryID int64
|
EntryID int64
|
||||||
TransactionID string
|
TransactionID string
|
||||||
BizType string
|
BizType string
|
||||||
AssetType string
|
AssetType string
|
||||||
AvailableDelta int64
|
AvailableDelta int64
|
||||||
FrozenDelta int64
|
FrozenDelta int64
|
||||||
AvailableAfter int64
|
AvailableAfter int64
|
||||||
FrozenAfter int64
|
FrozenAfter int64
|
||||||
CounterpartyUserID int64
|
CounterpartyUserID int64
|
||||||
RoomID string
|
RoomID string
|
||||||
CreatedAtMS int64
|
CreatedAtMS int64
|
||||||
|
TransferUSDMinor int64
|
||||||
|
TransferCurrencyCode string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListWalletTransactionsQuery 描述 App 钱包流水分页条件。
|
// ListWalletTransactionsQuery 描述 App 钱包流水分页条件。
|
||||||
|
|||||||
@ -2253,6 +2253,19 @@ func TestTransferSalaryToCoinSellerUsesRegionRateTier(t *testing.T) {
|
|||||||
if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ? AND JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$.biz_type')) = ?", first.TransactionID, "WalletBalanceChanged", "salary_transfer_to_coin_seller"); got != 2 {
|
if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ? AND JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$.biz_type')) = ?", first.TransactionID, "WalletBalanceChanged", "salary_transfer_to_coin_seller"); got != 2 {
|
||||||
t.Fatalf("salary transfer balance events must expose biz_type for statistics, got %d", got)
|
t.Fatalf("salary transfer balance events must expose biz_type for statistics, got %d", got)
|
||||||
}
|
}
|
||||||
|
transactions, total, err := svc.ListWalletTransactions(context.Background(), ledger.ListWalletTransactionsQuery{
|
||||||
|
AppCode: appcode.Default,
|
||||||
|
UserID: 35002,
|
||||||
|
AssetType: ledger.AssetCoinSellerCoin,
|
||||||
|
Page: 1,
|
||||||
|
PageSize: 10,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListWalletTransactions for seller failed: %v", err)
|
||||||
|
}
|
||||||
|
if total != 1 || len(transactions) != 1 || transactions[0].TransferUSDMinor != 4000 || transactions[0].TransferCurrencyCode != "USD" {
|
||||||
|
t.Fatalf("seller transfer amount history mismatch: total=%d transactions=%+v", total, transactions)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestTransferHostSalaryToCoinSellerCreditsTargetSellerOnly 验证别人主播把工资转给币商时,
|
// TestTransferHostSalaryToCoinSellerCreditsTargetSellerOnly 验证别人主播把工资转给币商时,
|
||||||
|
|||||||
@ -153,7 +153,8 @@ func (r *Repository) ListWalletTransactions(ctx context.Context, query ledger.Li
|
|||||||
rows, err := r.db.QueryContext(ctx, `
|
rows, err := r.db.QueryContext(ctx, `
|
||||||
SELECT e.entry_id, e.transaction_id, wt.biz_type, e.asset_type,
|
SELECT e.entry_id, e.transaction_id, wt.biz_type, e.asset_type,
|
||||||
e.available_delta, e.frozen_delta, e.available_after, e.frozen_after,
|
e.available_delta, e.frozen_delta, e.available_after, e.frozen_after,
|
||||||
e.counterparty_user_id, e.room_id, e.created_at_ms
|
e.counterparty_user_id, e.room_id, e.created_at_ms,
|
||||||
|
COALESCE(CAST(wt.metadata_json AS CHAR), '{}')
|
||||||
FROM wallet_entries e
|
FROM wallet_entries e
|
||||||
JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id
|
JOIN wallet_transactions wt ON wt.app_code = e.app_code AND wt.transaction_id = e.transaction_id
|
||||||
`+where+`
|
`+where+`
|
||||||
@ -169,6 +170,7 @@ func (r *Repository) ListWalletTransactions(ctx context.Context, query ledger.Li
|
|||||||
items := make([]ledger.WalletTransaction, 0, query.PageSize)
|
items := make([]ledger.WalletTransaction, 0, query.PageSize)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var item ledger.WalletTransaction
|
var item ledger.WalletTransaction
|
||||||
|
var metadataJSON string
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&item.EntryID,
|
&item.EntryID,
|
||||||
&item.TransactionID,
|
&item.TransactionID,
|
||||||
@ -181,10 +183,62 @@ func (r *Repository) ListWalletTransactions(ctx context.Context, query ledger.Li
|
|||||||
&item.CounterpartyUserID,
|
&item.CounterpartyUserID,
|
||||||
&item.RoomID,
|
&item.RoomID,
|
||||||
&item.CreatedAtMS,
|
&item.CreatedAtMS,
|
||||||
|
&metadataJSON,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
// H5 只需要展示用户实际转给币商的金额;这里统一从交易 metadata 提取安全字段,避免 gateway 或前端理解各类内部 JSON。
|
||||||
|
item.TransferUSDMinor, item.TransferCurrencyCode = walletTransactionTransferAmount(item.BizType, metadataJSON)
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
return items, total, rows.Err()
|
return items, total, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// walletTransactionTransferAmount 把工资转币商、H5 币商进货和后台进货的不同 metadata 统一成展示金额;解析失败时返回空值,避免流水页展示错误金额。
|
||||||
|
func walletTransactionTransferAmount(bizType string, metadataJSON string) (int64, string) {
|
||||||
|
var metadata struct {
|
||||||
|
SalaryUSDMinor int64 `json:"salary_usd_minor"`
|
||||||
|
RechargeUSDMinor int64 `json:"recharge_usd_minor"`
|
||||||
|
PaidAmountMicro int64 `json:"paid_amount_micro"`
|
||||||
|
PaidCurrencyCode string `json:"paid_currency_code"`
|
||||||
|
RechargeCurrencyCode string `json:"recharge_currency_code"`
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(metadataJSON) == "" {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
switch strings.TrimSpace(bizType) {
|
||||||
|
case bizTypeSalaryTransferToCoinSeller:
|
||||||
|
if metadata.SalaryUSDMinor > 0 {
|
||||||
|
return metadata.SalaryUSDMinor, "USD"
|
||||||
|
}
|
||||||
|
case "coin_seller_recharge":
|
||||||
|
if metadata.RechargeUSDMinor > 0 {
|
||||||
|
return metadata.RechargeUSDMinor, walletTransactionCurrency(metadata.RechargeCurrencyCode, "USD")
|
||||||
|
}
|
||||||
|
if metadata.PaidAmountMicro > 0 {
|
||||||
|
return metadata.PaidAmountMicro / 10000, walletTransactionCurrency(metadata.PaidCurrencyCode, "USD")
|
||||||
|
}
|
||||||
|
case bizTypeCoinSellerStockPurchase, bizTypeCoinSellerStockDeduction:
|
||||||
|
if metadata.PaidAmountMicro != 0 {
|
||||||
|
return metadata.PaidAmountMicro / 10000, walletTransactionCurrency(metadata.PaidCurrencyCode, "USD")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if metadata.SalaryUSDMinor > 0 {
|
||||||
|
return metadata.SalaryUSDMinor, "USD"
|
||||||
|
}
|
||||||
|
if metadata.RechargeUSDMinor > 0 {
|
||||||
|
return metadata.RechargeUSDMinor, walletTransactionCurrency(metadata.RechargeCurrencyCode, "USD")
|
||||||
|
}
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func walletTransactionCurrency(value string, fallback string) string {
|
||||||
|
value = strings.ToUpper(strings.TrimSpace(value))
|
||||||
|
if value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|||||||
@ -117,17 +117,19 @@ func (s *Server) ListWalletTransactions(ctx context.Context, req *walletv1.ListW
|
|||||||
resp := &walletv1.ListWalletTransactionsResponse{Transactions: make([]*walletv1.WalletTransaction, 0, len(items)), Total: total}
|
resp := &walletv1.ListWalletTransactionsResponse{Transactions: make([]*walletv1.WalletTransaction, 0, len(items)), Total: total}
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
resp.Transactions = append(resp.Transactions, &walletv1.WalletTransaction{
|
resp.Transactions = append(resp.Transactions, &walletv1.WalletTransaction{
|
||||||
EntryId: item.EntryID,
|
EntryId: item.EntryID,
|
||||||
TransactionId: item.TransactionID,
|
TransactionId: item.TransactionID,
|
||||||
BizType: item.BizType,
|
BizType: item.BizType,
|
||||||
AssetType: item.AssetType,
|
AssetType: item.AssetType,
|
||||||
AvailableDelta: item.AvailableDelta,
|
AvailableDelta: item.AvailableDelta,
|
||||||
FrozenDelta: item.FrozenDelta,
|
FrozenDelta: item.FrozenDelta,
|
||||||
AvailableAfter: item.AvailableAfter,
|
AvailableAfter: item.AvailableAfter,
|
||||||
FrozenAfter: item.FrozenAfter,
|
FrozenAfter: item.FrozenAfter,
|
||||||
CounterpartyUserId: item.CounterpartyUserID,
|
CounterpartyUserId: item.CounterpartyUserID,
|
||||||
RoomId: item.RoomID,
|
RoomId: item.RoomID,
|
||||||
CreatedAtMs: item.CreatedAtMS,
|
CreatedAtMs: item.CreatedAtMS,
|
||||||
|
TransferUsdMinor: item.TransferUSDMinor,
|
||||||
|
TransferCurrencyCode: item.TransferCurrencyCode,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user