fix coin seller stock deduction accounting
This commit is contained in:
parent
f455df9192
commit
ac767d1c7e
@ -1749,7 +1749,7 @@ func (x *AdminCreditAssetResponse) GetBalance() *AssetBalance {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminCreditCoinSellerStockRequest 是后台给 active 币商专用库存入账的账务命令。
|
// AdminCreditCoinSellerStockRequest 是后台调整币商专用库存的账务命令。
|
||||||
type AdminCreditCoinSellerStockRequest struct {
|
type AdminCreditCoinSellerStockRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
|
|||||||
@ -212,7 +212,7 @@ message AdminCreditAssetResponse {
|
|||||||
AssetBalance balance = 2;
|
AssetBalance balance = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminCreditCoinSellerStockRequest 是后台给 active 币商专用库存入账的账务命令。
|
// AdminCreditCoinSellerStockRequest 是后台调整币商专用库存的账务命令。
|
||||||
message AdminCreditCoinSellerStockRequest {
|
message AdminCreditCoinSellerStockRequest {
|
||||||
string command_id = 1;
|
string command_id = 1;
|
||||||
int64 seller_user_id = 2;
|
int64 seller_user_id = 2;
|
||||||
|
|||||||
@ -24,6 +24,7 @@ const (
|
|||||||
coinSellerTransferBizType = "coin_seller_transfer"
|
coinSellerTransferBizType = "coin_seller_transfer"
|
||||||
coinSellerRechargeBizType = "coin_seller_recharge"
|
coinSellerRechargeBizType = "coin_seller_recharge"
|
||||||
coinSellerStockPurchaseBizType = "coin_seller_stock_purchase"
|
coinSellerStockPurchaseBizType = "coin_seller_stock_purchase"
|
||||||
|
coinSellerStockDeductionBizType = "coin_seller_stock_deduction"
|
||||||
coinSellerCoinCompensationBizType = "coin_seller_coin_compensation"
|
coinSellerCoinCompensationBizType = "coin_seller_coin_compensation"
|
||||||
salaryTransferToCoinSellerBizType = "salary_transfer_to_coin_seller"
|
salaryTransferToCoinSellerBizType = "salary_transfer_to_coin_seller"
|
||||||
coinSellerLedgerTypeAdminStockCredit = "admin_stock_credit"
|
coinSellerLedgerTypeAdminStockCredit = "admin_stock_credit"
|
||||||
@ -782,6 +783,7 @@ func coinSellerLedgerBizTypes(ledgerType string) ([]string, error) {
|
|||||||
// 空类型表示“全部币商流水”,但仍只允许当前产品定义的公开币商库存口径,不能把其他内部账带出来。
|
// 空类型表示“全部币商流水”,但仍只允许当前产品定义的公开币商库存口径,不能把其他内部账带出来。
|
||||||
return []string{
|
return []string{
|
||||||
coinSellerStockPurchaseBizType,
|
coinSellerStockPurchaseBizType,
|
||||||
|
coinSellerStockDeductionBizType,
|
||||||
coinSellerRechargeBizType,
|
coinSellerRechargeBizType,
|
||||||
coinSellerCoinCompensationBizType,
|
coinSellerCoinCompensationBizType,
|
||||||
coinSellerTransferBizType,
|
coinSellerTransferBizType,
|
||||||
@ -790,7 +792,7 @@ func coinSellerLedgerBizTypes(ledgerType string) ([]string, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
case coinSellerLedgerTypeAdminStockCredit:
|
case coinSellerLedgerTypeAdminStockCredit:
|
||||||
// 进货操作是运营库存口径,底层包含后台 USDT 进货、H5 三方币商充值、金币补偿和后台扣除;资产条件仍限定在 COIN_SELLER_COIN。
|
// 进货操作是运营库存口径,底层包含后台 USDT 进货、H5 三方币商充值、金币补偿和后台扣除;资产条件仍限定在 COIN_SELLER_COIN。
|
||||||
return []string{coinSellerStockPurchaseBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType, coinManualCreditBizType}, nil
|
return []string{coinSellerStockPurchaseBizType, coinSellerStockDeductionBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType, coinManualCreditBizType}, nil
|
||||||
case coinSellerLedgerTypeSellerTransfer:
|
case coinSellerLedgerTypeSellerTransfer:
|
||||||
// 币商转用户只展示币商侧出账分录,收款用户资料在展示投影里补齐。
|
// 币商转用户只展示币商侧出账分录,收款用户资料在展示投影里补齐。
|
||||||
return []string{coinSellerTransferBizType}, nil
|
return []string{coinSellerTransferBizType}, nil
|
||||||
@ -804,7 +806,7 @@ func coinSellerLedgerBizTypes(ledgerType string) ([]string, error) {
|
|||||||
|
|
||||||
func coinSellerLedgerTypeForBizType(bizType string) string {
|
func coinSellerLedgerTypeForBizType(bizType string) string {
|
||||||
switch bizType {
|
switch bizType {
|
||||||
case coinSellerStockPurchaseBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType:
|
case coinSellerStockPurchaseBizType, coinSellerStockDeductionBizType, coinSellerRechargeBizType, coinSellerCoinCompensationBizType:
|
||||||
return coinSellerLedgerTypeAdminStockCredit
|
return coinSellerLedgerTypeAdminStockCredit
|
||||||
case coinManualCreditBizType:
|
case coinManualCreditBizType:
|
||||||
return coinSellerLedgerTypeAdminStockCredit
|
return coinSellerLedgerTypeAdminStockCredit
|
||||||
@ -821,6 +823,8 @@ func coinSellerLedgerLabel(item coinSellerLedgerDTO) string {
|
|||||||
switch item.BizType {
|
switch item.BizType {
|
||||||
case coinSellerStockPurchaseBizType:
|
case coinSellerStockPurchaseBizType:
|
||||||
return "USDT进货"
|
return "USDT进货"
|
||||||
|
case coinSellerStockDeductionBizType:
|
||||||
|
return "USDT扣除"
|
||||||
case coinSellerRechargeBizType:
|
case coinSellerRechargeBizType:
|
||||||
return "三方充值"
|
return "三方充值"
|
||||||
case coinSellerCoinCompensationBizType:
|
case coinSellerCoinCompensationBizType:
|
||||||
|
|||||||
@ -51,10 +51,10 @@ func TestCoinSellerLedgerWhereMapsAdminStockCredit(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("coin seller ledger where failed: %v", err)
|
t.Fatalf("coin seller ledger where failed: %v", err)
|
||||||
}
|
}
|
||||||
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?) AND e.user_id IN (?,?)"; where != want {
|
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?,?) AND e.user_id IN (?,?)"; where != want {
|
||||||
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
||||||
}
|
}
|
||||||
if len(args) != 8 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerRechargeBizType || args[4] != coinSellerCoinCompensationBizType || args[5] != coinManualCreditBizType || args[6] != int64(3001) || args[7] != int64(3002) {
|
if len(args) != 9 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerStockDeductionBizType || args[4] != coinSellerRechargeBizType || args[5] != coinSellerCoinCompensationBizType || args[6] != coinManualCreditBizType || args[7] != int64(3001) || args[8] != int64(3002) {
|
||||||
t.Fatalf("args mismatch: %#v", args)
|
t.Fatalf("args mismatch: %#v", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -64,10 +64,10 @@ func TestCoinSellerLedgerWhereEmptyTypeUsesAllPublicTypes(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("coin seller ledger where failed: %v", err)
|
t.Fatalf("coin seller ledger where failed: %v", err)
|
||||||
}
|
}
|
||||||
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?,?,?)"; where != want {
|
if want := "WHERE e.app_code = ? AND e.asset_type = ? AND wt.biz_type IN (?,?,?,?,?,?,?)"; where != want {
|
||||||
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
t.Fatalf("where mismatch:\nwant %s\n got %s", want, where)
|
||||||
}
|
}
|
||||||
if len(args) != 8 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerRechargeBizType || args[4] != coinSellerCoinCompensationBizType || args[5] != coinSellerTransferBizType || args[6] != salaryTransferToCoinSellerBizType || args[7] != coinManualCreditBizType {
|
if len(args) != 9 || args[0] != "lalu" || args[1] != coinSellerAssetType || args[2] != coinSellerStockPurchaseBizType || args[3] != coinSellerStockDeductionBizType || args[4] != coinSellerRechargeBizType || args[5] != coinSellerCoinCompensationBizType || args[6] != coinSellerTransferBizType || args[7] != salaryTransferToCoinSellerBizType || args[8] != coinManualCreditBizType {
|
||||||
t.Fatalf("args mismatch: %#v", args)
|
t.Fatalf("args mismatch: %#v", args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,6 +117,7 @@ func TestCoinSellerLedgerLabelUsesConcreteStockAndDebitType(t *testing.T) {
|
|||||||
want string
|
want string
|
||||||
}{
|
}{
|
||||||
{name: "usdt purchase", item: coinSellerLedgerDTO{BizType: coinSellerStockPurchaseBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "USDT进货"},
|
{name: "usdt purchase", item: coinSellerLedgerDTO{BizType: coinSellerStockPurchaseBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "USDT进货"},
|
||||||
|
{name: "usdt deduction", item: coinSellerLedgerDTO{BizType: coinSellerStockDeductionBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "USDT扣除"},
|
||||||
{name: "third party recharge", item: coinSellerLedgerDTO{BizType: coinSellerRechargeBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "三方充值"},
|
{name: "third party recharge", item: coinSellerLedgerDTO{BizType: coinSellerRechargeBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "三方充值"},
|
||||||
{name: "compensation", item: coinSellerLedgerDTO{BizType: coinSellerCoinCompensationBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "金币补偿"},
|
{name: "compensation", item: coinSellerLedgerDTO{BizType: coinSellerCoinCompensationBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit}, want: "金币补偿"},
|
||||||
{name: "debit", item: coinSellerLedgerDTO{BizType: coinManualCreditBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit, AvailableDelta: -100}, want: "金币扣除"},
|
{name: "debit", item: coinSellerLedgerDTO{BizType: coinManualCreditBizType, LedgerType: coinSellerLedgerTypeAdminStockCredit, AvailableDelta: -100}, want: "金币扣除"},
|
||||||
|
|||||||
@ -340,9 +340,9 @@ func (h *Handler) DebitCoinSellerStock(c *gin.Context) {
|
|||||||
response.BadRequest(c, err.Error())
|
response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
writeHostOrgAuditLog(c, h.audit, "coin-seller-stock-debit", "wallet_entries", sellerUserID,
|
writeHostOrgAuditLog(c, h.audit, "coin-seller-stock-debit", "coin_seller_stock_records", sellerUserID,
|
||||||
fmt.Sprintf("command_id=%s seller_user_id=%d coin_amount=%d transaction_id=%s reason=%q",
|
fmt.Sprintf("command_id=%s seller_user_id=%d stock_type=%s coin_amount=%d paid_currency_code=%s paid_amount_micro=%d transaction_id=%s reason=%q",
|
||||||
strings.TrimSpace(req.CommandID), sellerUserID, req.CoinAmount, result.GetTransactionId(), strings.TrimSpace(req.Reason)))
|
strings.TrimSpace(req.CommandID), sellerUserID, result.GetStockType(), result.GetCoinAmount(), result.GetPaidCurrencyCode(), result.GetPaidAmountMicro(), result.GetTransactionId(), strings.TrimSpace(req.Reason)))
|
||||||
response.Created(c, coinSellerStockDebitFromProto(sellerUserID, req.CoinAmount, result))
|
response.Created(c, coinSellerStockDebitFromProto(sellerUserID, req.CoinAmount, result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -171,10 +171,11 @@ type coinSellerStockCreditRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type coinSellerStockDebitRequest struct {
|
type coinSellerStockDebitRequest struct {
|
||||||
CommandID string `json:"commandId" binding:"required"`
|
CommandID string `json:"commandId" binding:"required"`
|
||||||
CoinAmount int64 `json:"coinAmount" binding:"required"`
|
CoinAmount int64 `json:"coinAmount" binding:"required"`
|
||||||
EvidenceRef string `json:"evidenceRef"`
|
RechargeAmount string `json:"rechargeAmount" binding:"required"`
|
||||||
Reason string `json:"reason" binding:"required"`
|
EvidenceRef string `json:"evidenceRef"`
|
||||||
|
Reason string `json:"reason" binding:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type replaceCoinSellerSalaryRatesRequest struct {
|
type replaceCoinSellerSalaryRatesRequest struct {
|
||||||
|
|||||||
@ -15,11 +15,16 @@ type coinSellerStockCreditResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type coinSellerStockDebitResponse struct {
|
type coinSellerStockDebitResponse struct {
|
||||||
TransactionID string `json:"transactionId"`
|
TransactionID string `json:"transactionId"`
|
||||||
SellerUserID int64 `json:"sellerUserId,string"`
|
SellerUserID int64 `json:"sellerUserId,string"`
|
||||||
CoinAmount int64 `json:"coinAmount"`
|
StockType string `json:"stockType"`
|
||||||
AvailableDelta int64 `json:"availableDelta"`
|
CoinAmount int64 `json:"coinAmount"`
|
||||||
BalanceAfter int64 `json:"balanceAfter"`
|
AvailableDelta int64 `json:"availableDelta"`
|
||||||
|
PaidCurrencyCode string `json:"paidCurrencyCode"`
|
||||||
|
PaidAmountMicro int64 `json:"paidAmountMicro"`
|
||||||
|
CountsAsSellerRecharge bool `json:"countsAsSellerRecharge"`
|
||||||
|
BalanceAfter int64 `json:"balanceAfter"`
|
||||||
|
CreatedAtMS int64 `json:"createdAtMs"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func coinSellerStockCreditFromProto(item *walletv1.AdminCreditCoinSellerStockResponse) coinSellerStockCreditResponse {
|
func coinSellerStockCreditFromProto(item *walletv1.AdminCreditCoinSellerStockResponse) coinSellerStockCreditResponse {
|
||||||
@ -39,19 +44,20 @@ func coinSellerStockCreditFromProto(item *walletv1.AdminCreditCoinSellerStockRes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func coinSellerStockDebitFromProto(sellerUserID int64, coinAmount int64, item *walletv1.AdminCreditAssetResponse) coinSellerStockDebitResponse {
|
func coinSellerStockDebitFromProto(sellerUserID int64, coinAmount int64, item *walletv1.AdminCreditCoinSellerStockResponse) coinSellerStockDebitResponse {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return coinSellerStockDebitResponse{SellerUserID: sellerUserID, CoinAmount: coinAmount, AvailableDelta: -coinAmount}
|
return coinSellerStockDebitResponse{SellerUserID: sellerUserID, CoinAmount: coinAmount, AvailableDelta: -coinAmount}
|
||||||
}
|
}
|
||||||
balanceAfter := int64(0)
|
|
||||||
if item.GetBalance() != nil {
|
|
||||||
balanceAfter = item.GetBalance().GetAvailableAmount()
|
|
||||||
}
|
|
||||||
return coinSellerStockDebitResponse{
|
return coinSellerStockDebitResponse{
|
||||||
TransactionID: item.GetTransactionId(),
|
TransactionID: item.GetTransactionId(),
|
||||||
SellerUserID: sellerUserID,
|
SellerUserID: sellerUserID,
|
||||||
CoinAmount: coinAmount,
|
StockType: item.GetStockType(),
|
||||||
AvailableDelta: -coinAmount,
|
CoinAmount: item.GetCoinAmount(),
|
||||||
BalanceAfter: balanceAfter,
|
AvailableDelta: item.GetCoinAmount(),
|
||||||
|
PaidCurrencyCode: item.GetPaidCurrencyCode(),
|
||||||
|
PaidAmountMicro: item.GetPaidAmountMicro(),
|
||||||
|
CountsAsSellerRecharge: item.GetCountsAsSellerRecharge(),
|
||||||
|
BalanceAfter: item.GetBalanceAfter(),
|
||||||
|
CreatedAtMS: item.GetCreatedAtMs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
coinSellerStatusActive = "active"
|
|
||||||
coinSellerMerchantAssetType = "COIN_SELLER_COIN"
|
coinSellerMerchantAssetType = "COIN_SELLER_COIN"
|
||||||
coinSellerStockTypePurchase = "usdt_purchase"
|
coinSellerStockTypePurchase = "usdt_purchase"
|
||||||
|
coinSellerStockTypeDeduction = "usdt_deduction"
|
||||||
coinSellerStockTypeCompensate = "coin_compensation"
|
coinSellerStockTypeCompensate = "coin_compensation"
|
||||||
paidCurrencyUSDT = "USDT"
|
paidCurrencyUSDT = "USDT"
|
||||||
sortByDiamond = "diamond"
|
sortByDiamond = "diamond"
|
||||||
@ -312,7 +312,7 @@ func (s *Service) CreditCoinSellerStock(ctx context.Context, actorID int64, sell
|
|||||||
return nil, fmt.Errorf("stock type is invalid")
|
return nil, fmt.Errorf("stock type is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
seller, err := s.activeCoinSellerUser(ctx, sellerUserID, requestID)
|
seller, err := s.coinSellerUser(ctx, sellerUserID, requestID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -334,7 +334,7 @@ func (s *Service) CreditCoinSellerStock(ctx context.Context, actorID int64, sell
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) DebitCoinSellerStock(ctx context.Context, actorID int64, sellerUserID int64, requestID string, req coinSellerStockDebitRequest) (*walletv1.AdminCreditAssetResponse, error) {
|
func (s *Service) DebitCoinSellerStock(ctx context.Context, actorID int64, sellerUserID int64, requestID string, req coinSellerStockDebitRequest) (*walletv1.AdminCreditCoinSellerStockResponse, error) {
|
||||||
if s.walletClient == nil {
|
if s.walletClient == nil {
|
||||||
return nil, fmt.Errorf("wallet client is not configured")
|
return nil, fmt.Errorf("wallet client is not configured")
|
||||||
}
|
}
|
||||||
@ -353,24 +353,33 @@ func (s *Service) DebitCoinSellerStock(ctx context.Context, actorID int64, selle
|
|||||||
if len(reason) > 512 || len(commandID) > 128 || len(evidenceRef) > 256 {
|
if len(reason) > 512 || len(commandID) > 128 || len(evidenceRef) > 256 {
|
||||||
return nil, fmt.Errorf("coin seller debit text fields are too long")
|
return nil, fmt.Errorf("coin seller debit text fields are too long")
|
||||||
}
|
}
|
||||||
if _, err := s.activeCoinSellerUser(ctx, sellerUserID, requestID); err != nil {
|
paidAmountMicro, err := parseUSDTAmountMicro(req.RechargeAmount)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// 币商扣除是运营对币商专用库存的负向调账:只扣 COIN_SELLER_COIN,不写普通 COIN,也不写进货记录。
|
seller, err := s.coinSellerUser(ctx, sellerUserID, requestID)
|
||||||
// wallet-service 的 AdminCreditAsset 负责余额不足校验、幂等冲突检测、wallet_entries 和 outbox 原子落账。
|
if err != nil {
|
||||||
return s.walletClient.AdminCreditAsset(ctx, &walletv1.AdminCreditAssetRequest{
|
return nil, err
|
||||||
CommandId: commandID,
|
}
|
||||||
TargetUserId: sellerUserID,
|
// 币商扣除按 USDT 进货冲回处理:钱包事务会同时写负向库存分录、负向进货记录和统计 outbox,
|
||||||
AssetType: coinSellerMerchantAssetType,
|
// 这样后台列表、币商流水和充值聚合都使用同一条账务事实,避免只扣余额却不冲收入。
|
||||||
Amount: -req.CoinAmount,
|
return s.walletClient.AdminCreditCoinSellerStock(ctx, &walletv1.AdminCreditCoinSellerStockRequest{
|
||||||
OperatorUserId: actorID,
|
CommandId: commandID,
|
||||||
Reason: reason,
|
SellerUserId: sellerUserID,
|
||||||
EvidenceRef: evidenceRef,
|
SellerCountryId: seller.CountryID,
|
||||||
AppCode: appctx.FromContext(ctx),
|
SellerRegionId: seller.RegionID,
|
||||||
|
StockType: coinSellerStockTypeDeduction,
|
||||||
|
CoinAmount: req.CoinAmount,
|
||||||
|
PaidCurrencyCode: paidCurrencyUSDT,
|
||||||
|
PaidAmountMicro: paidAmountMicro,
|
||||||
|
EvidenceRef: evidenceRef,
|
||||||
|
OperatorUserId: actorID,
|
||||||
|
Reason: reason,
|
||||||
|
AppCode: appctx.FromContext(ctx),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) activeCoinSellerUser(ctx context.Context, sellerUserID int64, requestID string) (*userclient.User, error) {
|
func (s *Service) coinSellerUser(ctx context.Context, sellerUserID int64, requestID string) (*userclient.User, error) {
|
||||||
if s == nil || s.userClient == nil {
|
if s == nil || s.userClient == nil {
|
||||||
return nil, fmt.Errorf("user client is not configured")
|
return nil, fmt.Errorf("user client is not configured")
|
||||||
}
|
}
|
||||||
@ -382,8 +391,8 @@ func (s *Service) activeCoinSellerUser(ctx context.Context, sellerUserID int64,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if profile == nil || profile.Status != coinSellerStatusActive || profile.MerchantAssetType != coinSellerMerchantAssetType {
|
if profile == nil || profile.MerchantAssetType != coinSellerMerchantAssetType {
|
||||||
return nil, fmt.Errorf("target user is not an active coin seller")
|
return nil, fmt.Errorf("target user is not a coin seller")
|
||||||
}
|
}
|
||||||
seller, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{
|
seller, err := s.userClient.GetUser(ctx, userclient.GetUserRequest{
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
|
|||||||
@ -436,7 +436,7 @@ func rebuildCoinSellerStock(ctx context.Context, db *sql.DB, state *rebuildState
|
|||||||
rows, err := db.QueryContext(ctx, `
|
rows, err := db.QueryContext(ctx, `
|
||||||
SELECT seller_user_id, paid_amount_micro, created_at_ms
|
SELECT seller_user_id, paid_amount_micro, created_at_ms
|
||||||
FROM coin_seller_stock_records
|
FROM coin_seller_stock_records
|
||||||
WHERE app_code = ? AND counts_as_seller_recharge = 1 AND paid_amount_micro > 0 AND created_at_ms >= ? AND created_at_ms < ?`,
|
WHERE app_code = ? AND counts_as_seller_recharge = 1 AND paid_amount_micro <> 0 AND created_at_ms >= ? AND created_at_ms < ?`,
|
||||||
app, startMS, endMS)
|
app, startMS, endMS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -287,7 +287,7 @@ func coinSellerStockEvent(body []byte) (mysqlstorage.CoinSellerStockEvent, bool,
|
|||||||
return mysqlstorage.CoinSellerStockEvent{}, false, nil
|
return mysqlstorage.CoinSellerStockEvent{}, false, nil
|
||||||
}
|
}
|
||||||
usdMinor := amountMicroToUSDMinor(mysqlstorage.Int64(payload, "paid_amount_micro"))
|
usdMinor := amountMicroToUSDMinor(mysqlstorage.Int64(payload, "paid_amount_micro"))
|
||||||
if usdMinor <= 0 {
|
if usdMinor == 0 {
|
||||||
return mysqlstorage.CoinSellerStockEvent{}, false, nil
|
return mysqlstorage.CoinSellerStockEvent{}, false, nil
|
||||||
}
|
}
|
||||||
return mysqlstorage.CoinSellerStockEvent{
|
return mysqlstorage.CoinSellerStockEvent{
|
||||||
@ -336,7 +336,7 @@ func isCoinSellerRechargeType(value string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func amountMicroToUSDMinor(amountMicro int64) int64 {
|
func amountMicroToUSDMinor(amountMicro int64) int64 {
|
||||||
if amountMicro <= 0 {
|
if amountMicro == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return amountMicro / 10_000
|
return amountMicro / 10_000
|
||||||
|
|||||||
@ -88,6 +88,31 @@ func TestLocalStatisticsDataScreenFlow(t *testing.T) {
|
|||||||
if err := repo.ConsumeCoinSellerStock(ctx, stock); err != nil {
|
if err := repo.ConsumeCoinSellerStock(ctx, stock); err != nil {
|
||||||
t.Fatalf("consume coin seller stock failed: %v", err)
|
t.Fatalf("consume coin seller stock failed: %v", err)
|
||||||
}
|
}
|
||||||
|
coinSellerStockDeduction := walletMessage(t, walletmq.WalletOutboxMessage{
|
||||||
|
AppCode: appCode,
|
||||||
|
EventID: "WalletCoinSellerStockPurchased:deduction:1",
|
||||||
|
EventType: "WalletCoinSellerStockPurchased",
|
||||||
|
TransactionID: "wallet_tx_coin_seller_stock_deduction_1",
|
||||||
|
CommandID: "coin_seller_stock_deduction_1",
|
||||||
|
UserID: userID + 10,
|
||||||
|
PayloadJSON: mustJSON(t, map[string]any{
|
||||||
|
"seller_user_id": userID + 10,
|
||||||
|
"country_id": countryID,
|
||||||
|
"region_id": regionID,
|
||||||
|
"coin_amount": int64(-50_000),
|
||||||
|
"paid_amount_micro": int64(-500_000),
|
||||||
|
"counts_as_seller_recharge": true,
|
||||||
|
"stock_type": "usdt_deduction",
|
||||||
|
}),
|
||||||
|
OccurredAtMS: occurredAt,
|
||||||
|
})
|
||||||
|
stockDeduction, ok, err := coinSellerStockEvent(coinSellerStockDeduction)
|
||||||
|
if err != nil || !ok {
|
||||||
|
t.Fatalf("decode coin seller stock deduction failed: ok=%v err=%v", ok, err)
|
||||||
|
}
|
||||||
|
if err := repo.ConsumeCoinSellerStock(ctx, stockDeduction); err != nil {
|
||||||
|
t.Fatalf("consume coin seller stock deduction failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
coinSellerRecharge := walletMessage(t, walletmq.WalletOutboxMessage{
|
coinSellerRecharge := walletMessage(t, walletmq.WalletOutboxMessage{
|
||||||
AppCode: appCode,
|
AppCode: appCode,
|
||||||
@ -206,7 +231,7 @@ func TestLocalStatisticsDataScreenFlow(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("query overview failed: %v", err)
|
t.Fatalf("query overview failed: %v", err)
|
||||||
}
|
}
|
||||||
if overview.GoogleRechargeUSDMinor != 150 || overview.CoinSellerRechargeUSDMinor != 400 || overview.CoinSellerTransferCoin != 160_000 || overview.RechargeUSDMinor != 550 || overview.NewUserRechargeUSDMinor != 150 || overview.PaidUsers != 2 {
|
if overview.GoogleRechargeUSDMinor != 150 || overview.CoinSellerRechargeUSDMinor != 350 || overview.CoinSellerTransferCoin != 160_000 || overview.RechargeUSDMinor != 500 || overview.NewUserRechargeUSDMinor != 150 || overview.PaidUsers != 2 {
|
||||||
t.Fatalf("google recharge metrics mismatch: %+v", overview)
|
t.Fatalf("google recharge metrics mismatch: %+v", overview)
|
||||||
}
|
}
|
||||||
if overview.LuckyGiftTurnover != 1_250 || overview.LuckyGiftPayout != 300 || overview.LuckyGiftProfit != 950 || overview.LuckyGiftPayers != 1 {
|
if overview.LuckyGiftTurnover != 1_250 || overview.LuckyGiftPayout != 300 || overview.LuckyGiftProfit != 950 || overview.LuckyGiftPayers != 1 {
|
||||||
|
|||||||
@ -130,6 +130,8 @@ func NormalizeCoinSellerStockType(stockType string) string {
|
|||||||
switch strings.ToLower(strings.TrimSpace(stockType)) {
|
switch strings.ToLower(strings.TrimSpace(stockType)) {
|
||||||
case StockTypeUSDTPurchase:
|
case StockTypeUSDTPurchase:
|
||||||
return StockTypeUSDTPurchase
|
return StockTypeUSDTPurchase
|
||||||
|
case StockTypeUSDTDeduction:
|
||||||
|
return StockTypeUSDTDeduction
|
||||||
case StockTypeCoinCompensation:
|
case StockTypeCoinCompensation:
|
||||||
return StockTypeCoinCompensation
|
return StockTypeCoinCompensation
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -26,6 +26,8 @@ const (
|
|||||||
|
|
||||||
// StockTypeUSDTPurchase 表示币商线下 USDT 进货后发放专用金币库存。
|
// StockTypeUSDTPurchase 表示币商线下 USDT 进货后发放专用金币库存。
|
||||||
StockTypeUSDTPurchase = "usdt_purchase"
|
StockTypeUSDTPurchase = "usdt_purchase"
|
||||||
|
// StockTypeUSDTDeduction 表示运营按 USDT 入金口径冲回币商专用金币库存和充值金额。
|
||||||
|
StockTypeUSDTDeduction = "usdt_deduction"
|
||||||
// StockTypeCoinCompensation 表示平台人工补偿币商专用金币库存,不计入进货金额。
|
// StockTypeCoinCompensation 表示平台人工补偿币商专用金币库存,不计入进货金额。
|
||||||
StockTypeCoinCompensation = "coin_compensation"
|
StockTypeCoinCompensation = "coin_compensation"
|
||||||
// PaidCurrencyUSDT 是首版币商进货支持的唯一线下付款币种。
|
// PaidCurrencyUSDT 是首版币商进货支持的唯一线下付款币种。
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminCreditCoinSellerStock 只给 COIN_SELLER_COIN 库存入账;USDT 进货和金币补偿使用不同账务类型。
|
// AdminCreditCoinSellerStock 只调整 COIN_SELLER_COIN 库存;USDT 进货、USDT 冲回和金币补偿使用不同账务类型。
|
||||||
func (s *Service) AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) {
|
func (s *Service) AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) {
|
||||||
if command.CommandID == "" || command.SellerUserID <= 0 || command.OperatorUserID <= 0 {
|
if command.CommandID == "" || command.SellerUserID <= 0 || command.OperatorUserID <= 0 {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock command is incomplete")
|
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock command is incomplete")
|
||||||
@ -34,12 +34,12 @@ func (s *Service) AdminCreditCoinSellerStock(ctx context.Context, command ledger
|
|||||||
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock text fields are too long")
|
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InvalidArgument, "coin seller stock text fields are too long")
|
||||||
}
|
}
|
||||||
switch command.StockType {
|
switch command.StockType {
|
||||||
case ledger.StockTypeUSDTPurchase:
|
case ledger.StockTypeUSDTPurchase, ledger.StockTypeUSDTDeduction:
|
||||||
if command.PaidCurrencyCode == "" {
|
if command.PaidCurrencyCode == "" {
|
||||||
command.PaidCurrencyCode = ledger.PaidCurrencyUSDT
|
command.PaidCurrencyCode = ledger.PaidCurrencyUSDT
|
||||||
}
|
}
|
||||||
if command.PaidCurrencyCode != ledger.PaidCurrencyUSDT || command.PaidAmountMicro <= 0 {
|
if command.PaidCurrencyCode != ledger.PaidCurrencyUSDT || command.PaidAmountMicro <= 0 {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockAmountInvalid, "usdt purchase requires paid amount")
|
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.CoinSellerStockAmountInvalid, "usdt stock adjustment requires paid amount")
|
||||||
}
|
}
|
||||||
case ledger.StockTypeCoinCompensation:
|
case ledger.StockTypeCoinCompensation:
|
||||||
if command.PaidCurrencyCode != "" || command.PaidAmountMicro != 0 || command.PaymentRef != "" {
|
if command.PaidCurrencyCode != "" || command.PaidAmountMicro != 0 || command.PaymentRef != "" {
|
||||||
|
|||||||
@ -2983,6 +2983,58 @@ func TestAdminCreditCoinSellerStockPurchaseCreditsDedicatedBalance(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAdminCreditCoinSellerStockDeductionDebitsDedicatedBalanceAndRechargeAmount 验证 USDT 扣除会同时冲回库存和币商充值金额。
|
||||||
|
func TestAdminCreditCoinSellerStockDeductionDebitsDedicatedBalanceAndRechargeAmount(t *testing.T) {
|
||||||
|
repository := mysqltest.NewRepository(t)
|
||||||
|
svc := walletservice.New(repository)
|
||||||
|
credit := ledger.CoinSellerStockCreditCommand{
|
||||||
|
CommandID: "cmd-stock-usdt-deduct-seed",
|
||||||
|
SellerUserID: 33011,
|
||||||
|
SellerCountryID: 63,
|
||||||
|
SellerRegionID: 7,
|
||||||
|
StockType: ledger.StockTypeUSDTPurchase,
|
||||||
|
CoinAmount: 8000000,
|
||||||
|
PaidAmountMicro: 100000000,
|
||||||
|
OperatorUserID: 90001,
|
||||||
|
}
|
||||||
|
if _, err := svc.AdminCreditCoinSellerStock(context.Background(), credit); err != nil {
|
||||||
|
t.Fatalf("seed stock purchase failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deduction := ledger.CoinSellerStockCreditCommand{
|
||||||
|
CommandID: "cmd-stock-usdt-deduct",
|
||||||
|
SellerUserID: 33011,
|
||||||
|
SellerCountryID: 63,
|
||||||
|
SellerRegionID: 7,
|
||||||
|
StockType: ledger.StockTypeUSDTDeduction,
|
||||||
|
CoinAmount: 3000000,
|
||||||
|
PaidAmountMicro: 37500000,
|
||||||
|
OperatorUserID: 90002,
|
||||||
|
Reason: "reverse bad stock",
|
||||||
|
}
|
||||||
|
first, err := svc.AdminCreditCoinSellerStock(context.Background(), deduction)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AdminCreditCoinSellerStock deduction failed: %v", err)
|
||||||
|
}
|
||||||
|
second, err := svc.AdminCreditCoinSellerStock(context.Background(), deduction)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AdminCreditCoinSellerStock deduction retry failed: %v", err)
|
||||||
|
}
|
||||||
|
if first.TransactionID == "" || first.TransactionID != second.TransactionID || first.BalanceAfter != 5000000 || first.CoinAmount != -3000000 || first.PaidAmountMicro != -37500000 || !first.CountsAsSellerRecharge {
|
||||||
|
t.Fatalf("stock deduction receipt mismatch: first=%+v second=%+v", first, second)
|
||||||
|
}
|
||||||
|
assertBalance(t, svc, 33011, ledger.AssetCoinSellerCoin, 5000000)
|
||||||
|
if got := repository.CountRows("coin_seller_stock_records", "transaction_id = ? AND stock_type = ? AND counts_as_seller_recharge = TRUE AND coin_amount = ? AND paid_amount_micro = ?", first.TransactionID, ledger.StockTypeUSDTDeduction, int64(-3000000), int64(-37500000)); got != 1 {
|
||||||
|
t.Fatalf("stock deduction should write one negative seller recharge record, got %d", got)
|
||||||
|
}
|
||||||
|
if got := repository.CountRows("wallet_entries", "transaction_id = ? AND available_delta = ?", first.TransactionID, int64(-3000000)); got != 1 {
|
||||||
|
t.Fatalf("stock deduction should write one negative wallet entry, got %d", got)
|
||||||
|
}
|
||||||
|
if got := repository.CountRows("wallet_outbox", "transaction_id = ? AND event_type = ? AND available_delta = ? AND JSON_EXTRACT(payload_json, '$.paid_amount_micro') = ?", first.TransactionID, "WalletCoinSellerStockPurchased", int64(-3000000), int64(-37500000)); got != 1 {
|
||||||
|
t.Fatalf("stock deduction should emit negative seller stock outbox event, got %d", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount 锁定补偿不能携带充值金额的边界。
|
// TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount 锁定补偿不能携带充值金额的边界。
|
||||||
func TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount(t *testing.T) {
|
func TestAdminCreditCoinSellerStockCompensationRejectsPaidAmount(t *testing.T) {
|
||||||
repository := mysqltest.NewRepository(t)
|
repository := mysqltest.NewRepository(t)
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import (
|
|||||||
"hyapp/services/wallet-service/internal/domain/ledger"
|
"hyapp/services/wallet-service/internal/domain/ledger"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminCreditCoinSellerStock 在同一事务里给币商 COIN_SELLER_COIN 库存入账,并记录专用进货明细。
|
// AdminCreditCoinSellerStock 在同一事务里调整币商 COIN_SELLER_COIN 库存,并记录专用进货/冲回明细。
|
||||||
func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) {
|
func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command ledger.CoinSellerStockCreditCommand) (ledger.CoinSellerStockCreditReceipt, error) {
|
||||||
if r == nil || r.db == nil {
|
if r == nil || r.db == nil {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
|
||||||
@ -49,26 +49,32 @@ func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command led
|
|||||||
}
|
}
|
||||||
|
|
||||||
nowMs := time.Now().UnixMilli()
|
nowMs := time.Now().UnixMilli()
|
||||||
account, err := r.lockAccount(ctx, tx, command.SellerUserID, ledger.AssetCoinSellerCoin, true, nowMs)
|
availableDelta := coinSellerStockAvailableDelta(command)
|
||||||
|
account, err := r.lockAccount(ctx, tx, command.SellerUserID, ledger.AssetCoinSellerCoin, availableDelta > 0, nowMs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, err
|
return ledger.CoinSellerStockCreditReceipt{}, err
|
||||||
}
|
}
|
||||||
balanceAfter, err := checkedAdd(account.AvailableAmount, command.CoinAmount)
|
if availableDelta < 0 && account.AvailableAmount < command.CoinAmount {
|
||||||
|
return ledger.CoinSellerStockCreditReceipt{}, xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
||||||
|
}
|
||||||
|
balanceAfter, err := checkedAdd(account.AvailableAmount, availableDelta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, err
|
return ledger.CoinSellerStockCreditReceipt{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionID := transactionID(command.AppCode, command.CommandID)
|
transactionID := transactionID(command.AppCode, command.CommandID)
|
||||||
countsAsSellerRecharge := command.StockType == ledger.StockTypeUSDTPurchase
|
countsAsSellerRecharge := command.StockType == ledger.StockTypeUSDTPurchase || command.StockType == ledger.StockTypeUSDTDeduction
|
||||||
|
recordCoinAmount := availableDelta
|
||||||
|
recordPaidAmountMicro := coinSellerStockPaidAmountMicro(command)
|
||||||
metadata := coinSellerStockMetadata{
|
metadata := coinSellerStockMetadata{
|
||||||
AppCode: command.AppCode,
|
AppCode: command.AppCode,
|
||||||
SellerUserID: command.SellerUserID,
|
SellerUserID: command.SellerUserID,
|
||||||
SellerCountryID: command.SellerCountryID,
|
SellerCountryID: command.SellerCountryID,
|
||||||
SellerRegionID: command.SellerRegionID,
|
SellerRegionID: command.SellerRegionID,
|
||||||
StockType: command.StockType,
|
StockType: command.StockType,
|
||||||
CoinAmount: command.CoinAmount,
|
CoinAmount: recordCoinAmount,
|
||||||
PaidCurrencyCode: command.PaidCurrencyCode,
|
PaidCurrencyCode: command.PaidCurrencyCode,
|
||||||
PaidAmountMicro: command.PaidAmountMicro,
|
PaidAmountMicro: recordPaidAmountMicro,
|
||||||
PaymentRef: command.PaymentRef,
|
PaymentRef: command.PaymentRef,
|
||||||
EvidenceRef: command.EvidenceRef,
|
EvidenceRef: command.EvidenceRef,
|
||||||
OperatorUserID: command.OperatorUserID,
|
OperatorUserID: command.OperatorUserID,
|
||||||
@ -85,14 +91,14 @@ func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command led
|
|||||||
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizType, requestHash, externalRef, metadata, nowMs); err != nil {
|
if err := r.insertTransaction(ctx, tx, transactionID, command.CommandID, bizType, requestHash, externalRef, metadata, nowMs); err != nil {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, err
|
return ledger.CoinSellerStockCreditReceipt{}, err
|
||||||
}
|
}
|
||||||
if err := r.applyAccountDelta(ctx, tx, account, command.CoinAmount, 0, nowMs); err != nil {
|
if err := r.applyAccountDelta(ctx, tx, account, availableDelta, 0, nowMs); err != nil {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, err
|
return ledger.CoinSellerStockCreditReceipt{}, err
|
||||||
}
|
}
|
||||||
if err := r.insertEntry(ctx, tx, walletEntry{
|
if err := r.insertEntry(ctx, tx, walletEntry{
|
||||||
TransactionID: transactionID,
|
TransactionID: transactionID,
|
||||||
UserID: command.SellerUserID,
|
UserID: command.SellerUserID,
|
||||||
AssetType: ledger.AssetCoinSellerCoin,
|
AssetType: ledger.AssetCoinSellerCoin,
|
||||||
AvailableDelta: command.CoinAmount,
|
AvailableDelta: availableDelta,
|
||||||
FrozenDelta: 0,
|
FrozenDelta: 0,
|
||||||
AvailableAfter: balanceAfter,
|
AvailableAfter: balanceAfter,
|
||||||
FrozenAfter: account.FrozenAmount,
|
FrozenAfter: account.FrozenAmount,
|
||||||
@ -104,7 +110,7 @@ func (r *Repository) AdminCreditCoinSellerStock(ctx context.Context, command led
|
|||||||
return ledger.CoinSellerStockCreditReceipt{}, err
|
return ledger.CoinSellerStockCreditReceipt{}, err
|
||||||
}
|
}
|
||||||
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
if err := r.insertWalletOutbox(ctx, tx, []walletOutboxEvent{
|
||||||
balanceChangedEvent(transactionID, command.CommandID, command.SellerUserID, ledger.AssetCoinSellerCoin, command.CoinAmount, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
balanceChangedEvent(transactionID, command.CommandID, command.SellerUserID, ledger.AssetCoinSellerCoin, availableDelta, 0, balanceAfter, account.FrozenAmount, account.Version+1, metadata, nowMs),
|
||||||
coinSellerStockCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
coinSellerStockCreditedEvent(transactionID, command.CommandID, metadata, nowMs),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return ledger.CoinSellerStockCreditReceipt{}, err
|
return ledger.CoinSellerStockCreditReceipt{}, err
|
||||||
@ -669,9 +675,25 @@ func coinSellerStockBizType(stockType string) string {
|
|||||||
switch stockType {
|
switch stockType {
|
||||||
case ledger.StockTypeUSDTPurchase:
|
case ledger.StockTypeUSDTPurchase:
|
||||||
return bizTypeCoinSellerStockPurchase
|
return bizTypeCoinSellerStockPurchase
|
||||||
|
case ledger.StockTypeUSDTDeduction:
|
||||||
|
return bizTypeCoinSellerStockDeduction
|
||||||
case ledger.StockTypeCoinCompensation:
|
case ledger.StockTypeCoinCompensation:
|
||||||
return bizTypeCoinSellerCoinCompensation
|
return bizTypeCoinSellerCoinCompensation
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func coinSellerStockAvailableDelta(command ledger.CoinSellerStockCreditCommand) int64 {
|
||||||
|
if command.StockType == ledger.StockTypeUSDTDeduction {
|
||||||
|
return -command.CoinAmount
|
||||||
|
}
|
||||||
|
return command.CoinAmount
|
||||||
|
}
|
||||||
|
|
||||||
|
func coinSellerStockPaidAmountMicro(command ledger.CoinSellerStockCreditCommand) int64 {
|
||||||
|
if command.StockType == ledger.StockTypeUSDTDeduction {
|
||||||
|
return -command.PaidAmountMicro
|
||||||
|
}
|
||||||
|
return command.PaidAmountMicro
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ const (
|
|||||||
bizTypeAgencyOpeningReward = "agency_opening_reward"
|
bizTypeAgencyOpeningReward = "agency_opening_reward"
|
||||||
bizTypeCoinSellerTransfer = "coin_seller_transfer"
|
bizTypeCoinSellerTransfer = "coin_seller_transfer"
|
||||||
bizTypeCoinSellerStockPurchase = "coin_seller_stock_purchase"
|
bizTypeCoinSellerStockPurchase = "coin_seller_stock_purchase"
|
||||||
|
bizTypeCoinSellerStockDeduction = "coin_seller_stock_deduction"
|
||||||
bizTypeCoinSellerCoinCompensation = "coin_seller_coin_compensation"
|
bizTypeCoinSellerCoinCompensation = "coin_seller_coin_compensation"
|
||||||
bizTypeSalaryExchangeToCoin = "salary_exchange_to_coin"
|
bizTypeSalaryExchangeToCoin = "salary_exchange_to_coin"
|
||||||
bizTypeSalaryTransferToCoinSeller = "salary_transfer_to_coin_seller"
|
bizTypeSalaryTransferToCoinSeller = "salary_transfer_to_coin_seller"
|
||||||
|
|||||||
@ -608,7 +608,7 @@ func agencyOpeningRewardCreditedEvent(transactionID string, commandID string, me
|
|||||||
|
|
||||||
func coinSellerStockCreditedEvent(transactionID string, commandID string, metadata coinSellerStockMetadata, nowMs int64) walletOutboxEvent {
|
func coinSellerStockCreditedEvent(transactionID string, commandID string, metadata coinSellerStockMetadata, nowMs int64) walletOutboxEvent {
|
||||||
eventType := "WalletCoinSellerCoinCompensated"
|
eventType := "WalletCoinSellerCoinCompensated"
|
||||||
if metadata.StockType == ledger.StockTypeUSDTPurchase {
|
if metadata.StockType == ledger.StockTypeUSDTPurchase || metadata.StockType == ledger.StockTypeUSDTDeduction {
|
||||||
eventType = "WalletCoinSellerStockPurchased"
|
eventType = "WalletCoinSellerStockPurchased"
|
||||||
}
|
}
|
||||||
return walletOutboxEvent{
|
return walletOutboxEvent{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user