239 lines
8.6 KiB
Go
239 lines
8.6 KiB
Go
package http
|
|
|
|
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 !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: authRequestMeta(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, 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,
|
|
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: authRequestMeta(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, bool) {
|
|
sellerResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
|
|
Meta: authRequestMeta(request, ""),
|
|
UserId: sellerUserID,
|
|
})
|
|
if err != nil {
|
|
httpkit.WriteRPCError(writer, request, err)
|
|
return 0, 0, false
|
|
}
|
|
targetResp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{
|
|
Meta: authRequestMeta(request, ""),
|
|
UserId: targetUserID,
|
|
})
|
|
if err != nil {
|
|
httpkit.WriteRPCError(writer, request, err)
|
|
return 0, 0, false
|
|
}
|
|
sellerRegionID := sellerResp.GetUser().GetRegionId()
|
|
targetRegionID := targetResp.GetUser().GetRegionId()
|
|
if sellerRegionID <= 0 || targetRegionID <= 0 {
|
|
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
|
return 0, 0, false
|
|
}
|
|
if sellerRegionID != targetRegionID {
|
|
// 充值政策当前按单一区域生效;跨区转账必须先设计明确的结算区域,不能静默套用任意一方政策。
|
|
httpkit.WriteError(writer, request, http.StatusConflict, string(xerr.Conflict), "conflict")
|
|
return 0, 0, false
|
|
}
|
|
return sellerRegionID, targetRegionID, 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
|
|
}
|