251 lines
9.2 KiB
Go

package walletapi
import (
"hyapp/services/gateway-service/internal/transport/http/httpkit"
"net/http"
"strconv"
"strings"
userv1 "hyapp.local/api/proto/user/v1"
walletv1 "hyapp.local/api/proto/wallet/v1"
"hyapp/pkg/appcode"
"hyapp/pkg/xerr"
"hyapp/services/gateway-service/internal/auth"
)
type assetBalanceData struct {
AssetType string `json:"asset_type"`
AvailableAmount int64 `json:"available_amount"`
FrozenAmount int64 `json:"frozen_amount"`
Version int64 `json:"version"`
}
type coinSellerTransferData struct {
TransactionID string `json:"transaction_id"`
SellerBalanceAfter int64 `json:"seller_balance_after"`
TargetBalanceAfter int64 `json:"target_balance_after"`
Amount int64 `json:"amount"`
RechargeUSDMinor int64 `json:"recharge_usd_minor"`
RechargeCurrencyCode string `json:"recharge_currency_code"`
RechargePolicyID int64 `json:"recharge_policy_id"`
RechargePolicyVersion string `json:"recharge_policy_version"`
RechargePolicyCoinAmount int64 `json:"recharge_policy_coin_amount"`
RechargePolicyUSDMinorAmount int64 `json:"recharge_policy_usd_minor_amount"`
}
type coinSellerTransferRequestBody struct {
CommandID string `json:"command_id"`
CommandIDCamel string `json:"commandId"`
TargetUserID int64 `json:"target_user_id"`
TargetUserIDCamel int64 `json:"targetUserId"`
TargetDisplayUserID string `json:"target_display_user_id"`
TargetDisplayUserIDCamel string `json:"targetDisplayUserId"`
Amount int64 `json:"amount"`
Reason string `json:"reason"`
}
func (b coinSellerTransferRequestBody) normalizedCommandID() string {
if commandID := strings.TrimSpace(b.CommandID); commandID != "" {
return commandID
}
return strings.TrimSpace(b.CommandIDCamel)
}
func (b coinSellerTransferRequestBody) normalizedTargetDisplayUserID() string {
if displayUserID := strings.TrimSpace(b.TargetDisplayUserID); displayUserID != "" {
return displayUserID
}
if displayUserID := strings.TrimSpace(b.TargetDisplayUserIDCamel); displayUserID != "" {
return displayUserID
}
if b.TargetUserID > 0 {
return strconv.FormatInt(b.TargetUserID, 10)
}
if b.TargetUserIDCamel > 0 {
return strconv.FormatInt(b.TargetUserIDCamel, 10)
}
return ""
}
// getMyBalances 返回当前登录用户的钱包余额。
func (h *Handler) getMyBalances(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
resp, err := h.walletClient.GetBalances(request.Context(), &walletv1.GetBalancesRequest{
RequestId: httpkit.RequestIDFromContext(request.Context()),
UserId: auth.UserIDFromContext(request.Context()),
AssetTypes: walletAssetTypes(request),
AppCode: appcode.FromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
balances := make([]assetBalanceData, 0, len(resp.GetBalances()))
for _, balance := range resp.GetBalances() {
balances = append(balances, assetBalanceData{
AssetType: balance.GetAssetType(),
AvailableAmount: balance.GetAvailableAmount(),
FrozenAmount: balance.GetFrozenAmount(),
Version: balance.GetVersion(),
})
}
httpkit.WriteOK(writer, request, map[string]any{"balances": balances})
}
// transferCoinFromSeller 处理币商给玩家转金币的 App 入口。
func (h *Handler) transferCoinFromSeller(writer http.ResponseWriter, request *http.Request) {
if h.walletClient == nil || h.userHostClient == nil || h.userProfileClient == nil || h.userIdentityClient == nil {
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return
}
var body coinSellerTransferRequestBody
if !httpkit.Decode(writer, request, &body) {
return
}
commandID := body.normalizedCommandID()
targetDisplayUserID := body.normalizedTargetDisplayUserID()
reason := strings.TrimSpace(body.Reason)
if commandID == "" || targetDisplayUserID == "" || body.Amount <= 0 || reason == "" {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return
}
sellerUserID := auth.UserIDFromContext(request.Context())
profileResp, err := h.userHostClient.GetCoinSellerProfile(request.Context(), &userv1.GetCoinSellerProfileRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: sellerUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
profile := profileResp.GetCoinSellerProfile()
if profile == nil || profile.GetStatus() != "active" || profile.GetMerchantAssetType() != "COIN_SELLER_COIN" {
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
return
}
targetUserID, ok := h.resolveCoinSellerTransferTargetUserID(writer, request, targetDisplayUserID)
if !ok {
return
}
sellerRegionID, targetRegionID, targetCountryID, ok := h.coinSellerTransferRegions(writer, request, sellerUserID, targetUserID)
if !ok {
return
}
resp, err := h.walletClient.TransferCoinFromSeller(request.Context(), &walletv1.TransferCoinFromSellerRequest{
CommandId: commandID,
SellerUserId: sellerUserID,
TargetUserId: targetUserID,
SellerRegionId: sellerRegionID,
TargetRegionId: targetRegionID,
TargetCountryId: targetCountryID,
Amount: body.Amount,
Reason: reason,
AppCode: appcode.FromContext(request.Context()),
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return
}
httpkit.WriteOK(writer, request, coinSellerTransferData{
TransactionID: resp.GetTransactionId(),
SellerBalanceAfter: resp.GetSellerBalanceAfter(),
TargetBalanceAfter: resp.GetTargetBalanceAfter(),
Amount: resp.GetAmount(),
RechargeUSDMinor: resp.GetRechargeUsdMinor(),
RechargeCurrencyCode: resp.GetRechargeCurrencyCode(),
RechargePolicyID: resp.GetRechargePolicyId(),
RechargePolicyVersion: resp.GetRechargePolicyVersion(),
RechargePolicyCoinAmount: resp.GetRechargePolicyCoinAmount(),
RechargePolicyUSDMinorAmount: resp.GetRechargePolicyUsdMinorAmount(),
})
}
func (h *Handler) resolveCoinSellerTransferTargetUserID(writer http.ResponseWriter, request *http.Request, targetDisplayUserID string) (int64, bool) {
resp, err := h.userIdentityClient.ResolveDisplayUserID(request.Context(), &userv1.ResolveDisplayUserIDRequest{
Meta: httpkit.UserMeta(request, ""),
DisplayUserId: targetDisplayUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return 0, false
}
identity := resp.GetIdentity()
targetUserID := identity.GetUserId()
if targetUserID <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return 0, false
}
return targetUserID, true
}
func (h *Handler) coinSellerTransferRegions(writer http.ResponseWriter, request *http.Request, sellerUserID int64, targetUserID int64) (int64, int64, int64, bool) {
sellerResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: sellerUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return 0, 0, 0, false
}
seller := sellerResp.GetUser()
sellerCountryID := seller.GetCountryId()
sellerRegionID := seller.GetRegionId()
if sellerUserID == targetUserID {
if sellerCountryID <= 0 || sellerRegionID <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return 0, 0, 0, false
}
return sellerRegionID, sellerRegionID, sellerCountryID, true
}
targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: targetUserID,
})
if err != nil {
httpkit.WriteRPCError(writer, request, err)
return 0, 0, 0, false
}
target := targetResp.GetUser()
targetCountryID := target.GetCountryId()
targetRegionID := target.GetRegionId()
if sellerRegionID <= 0 || targetCountryID <= 0 || targetRegionID <= 0 {
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
return 0, 0, 0, false
}
if sellerRegionID != targetRegionID {
// 充值政策当前按单一区域生效;跨区转账必须先设计明确的结算区域,不能静默套用任意一方政策。
httpkit.WriteError(writer, request, http.StatusConflict, string(xerr.Conflict), "conflict")
return 0, 0, 0, false
}
return sellerRegionID, targetRegionID, targetCountryID, true
}
func walletAssetTypes(request *http.Request) []string {
rawValues := request.URL.Query()["asset_type"]
if len(rawValues) == 0 {
rawValues = request.URL.Query()["asset_types"]
}
assetTypes := make([]string, 0, len(rawValues))
for _, raw := range rawValues {
for value := range strings.SplitSeq(raw, ",") {
value = strings.TrimSpace(value)
if value != "" {
assetTypes = append(assetTypes, value)
}
}
}
if len(assetTypes) == 0 {
return []string{"COIN"}
}
return assetTypes
}