财务模块

This commit is contained in:
zhx 2026-07-03 13:09:01 +08:00
parent a1e7514af3
commit 0480304483
21 changed files with 4557 additions and 3022 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1037,6 +1037,17 @@ message RechargeBill {
int64 exchange_usd_minor_amount = 17; int64 exchange_usd_minor_amount = 17;
int64 created_at_ms = 18; int64 created_at_ms = 18;
int64 provider_amount_minor = 19; int64 provider_amount_minor = 19;
// provider_code google_play/mifapay/v5pay/usdt_trc20
string provider_code = 20;
// user_paid_* Google Play Orders API
string user_paid_currency_code = 21;
int64 user_paid_amount_micro = 22;
// provider_fee/tax/net 0
int64 provider_fee_micro = 23;
int64 provider_tax_micro = 24;
int64 provider_net_micro = 25;
// paid_synced_at_ms Google Orders API 0
int64 paid_synced_at_ms = 26;
} }
message ListRechargeBillsRequest { message ListRechargeBillsRequest {
@ -1059,6 +1070,51 @@ message ListRechargeBillsResponse {
int64 total = 2; int64 total = 2;
} }
message GetRechargeBillSummaryRequest {
string request_id = 1;
string app_code = 2;
int64 user_id = 3;
int64 seller_user_id = 4;
int64 region_id = 5;
string recharge_type = 6;
string status = 7;
string keyword = 8;
int64 start_at_ms = 9;
int64 end_at_ms = 10;
}
message RechargeBillSummaryBucket {
int64 bill_count = 1;
int64 coin_amount = 2;
int64 usd_minor_amount = 3;
}
message GetRechargeBillSummaryResponse {
RechargeBillSummaryBucket total = 1;
RechargeBillSummaryBucket google_play = 2;
RechargeBillSummaryBucket third_party = 3;
}
message RefreshGooglePaymentPricesRequest {
string request_id = 1;
string app_code = 2;
repeated string transaction_ids = 3;
}
message GooglePaymentPrice {
string transaction_id = 1;
string currency_code = 2;
int64 paid_amount_micro = 3;
int64 tax_micro = 4;
int64 net_micro = 5;
int64 synced_at_ms = 6;
string error = 7;
}
message RefreshGooglePaymentPricesResponse {
repeated GooglePaymentPrice prices = 1;
}
// WalletFeatureFlags 使 wallet-service // WalletFeatureFlags 使 wallet-service
message WalletFeatureFlags { message WalletFeatureFlags {
bool recharge_enabled = 1; bool recharge_enabled = 1;
@ -2163,6 +2219,8 @@ service WalletService {
rpc ListResourceShopPurchaseOrders(ListResourceShopPurchaseOrdersRequest) returns (ListResourceShopPurchaseOrdersResponse); rpc ListResourceShopPurchaseOrders(ListResourceShopPurchaseOrdersRequest) returns (ListResourceShopPurchaseOrdersResponse);
rpc PurchaseResourceShopItem(PurchaseResourceShopItemRequest) returns (PurchaseResourceShopItemResponse); rpc PurchaseResourceShopItem(PurchaseResourceShopItemRequest) returns (PurchaseResourceShopItemResponse);
rpc ListRechargeBills(ListRechargeBillsRequest) returns (ListRechargeBillsResponse); rpc ListRechargeBills(ListRechargeBillsRequest) returns (ListRechargeBillsResponse);
rpc GetRechargeBillSummary(GetRechargeBillSummaryRequest) returns (GetRechargeBillSummaryResponse);
rpc RefreshGooglePaymentPrices(RefreshGooglePaymentPricesRequest) returns (RefreshGooglePaymentPricesResponse);
rpc GetWalletOverview(GetWalletOverviewRequest) returns (GetWalletOverviewResponse); rpc GetWalletOverview(GetWalletOverviewRequest) returns (GetWalletOverviewResponse);
rpc GetWalletValueSummary(GetWalletValueSummaryRequest) returns (GetWalletValueSummaryResponse); rpc GetWalletValueSummary(GetWalletValueSummaryRequest) returns (GetWalletValueSummaryResponse);
rpc GetUserGiftWall(GetUserGiftWallRequest) returns (GetUserGiftWallResponse); rpc GetUserGiftWall(GetUserGiftWallRequest) returns (GetUserGiftWallResponse);

View File

@ -248,6 +248,8 @@ const (
WalletService_ListResourceShopPurchaseOrders_FullMethodName = "/hyapp.wallet.v1.WalletService/ListResourceShopPurchaseOrders" WalletService_ListResourceShopPurchaseOrders_FullMethodName = "/hyapp.wallet.v1.WalletService/ListResourceShopPurchaseOrders"
WalletService_PurchaseResourceShopItem_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseResourceShopItem" WalletService_PurchaseResourceShopItem_FullMethodName = "/hyapp.wallet.v1.WalletService/PurchaseResourceShopItem"
WalletService_ListRechargeBills_FullMethodName = "/hyapp.wallet.v1.WalletService/ListRechargeBills" WalletService_ListRechargeBills_FullMethodName = "/hyapp.wallet.v1.WalletService/ListRechargeBills"
WalletService_GetRechargeBillSummary_FullMethodName = "/hyapp.wallet.v1.WalletService/GetRechargeBillSummary"
WalletService_RefreshGooglePaymentPrices_FullMethodName = "/hyapp.wallet.v1.WalletService/RefreshGooglePaymentPrices"
WalletService_GetWalletOverview_FullMethodName = "/hyapp.wallet.v1.WalletService/GetWalletOverview" WalletService_GetWalletOverview_FullMethodName = "/hyapp.wallet.v1.WalletService/GetWalletOverview"
WalletService_GetWalletValueSummary_FullMethodName = "/hyapp.wallet.v1.WalletService/GetWalletValueSummary" WalletService_GetWalletValueSummary_FullMethodName = "/hyapp.wallet.v1.WalletService/GetWalletValueSummary"
WalletService_GetUserGiftWall_FullMethodName = "/hyapp.wallet.v1.WalletService/GetUserGiftWall" WalletService_GetUserGiftWall_FullMethodName = "/hyapp.wallet.v1.WalletService/GetUserGiftWall"
@ -350,6 +352,8 @@ type WalletServiceClient interface {
ListResourceShopPurchaseOrders(ctx context.Context, in *ListResourceShopPurchaseOrdersRequest, opts ...grpc.CallOption) (*ListResourceShopPurchaseOrdersResponse, error) ListResourceShopPurchaseOrders(ctx context.Context, in *ListResourceShopPurchaseOrdersRequest, opts ...grpc.CallOption) (*ListResourceShopPurchaseOrdersResponse, error)
PurchaseResourceShopItem(ctx context.Context, in *PurchaseResourceShopItemRequest, opts ...grpc.CallOption) (*PurchaseResourceShopItemResponse, error) PurchaseResourceShopItem(ctx context.Context, in *PurchaseResourceShopItemRequest, opts ...grpc.CallOption) (*PurchaseResourceShopItemResponse, error)
ListRechargeBills(ctx context.Context, in *ListRechargeBillsRequest, opts ...grpc.CallOption) (*ListRechargeBillsResponse, error) ListRechargeBills(ctx context.Context, in *ListRechargeBillsRequest, opts ...grpc.CallOption) (*ListRechargeBillsResponse, error)
GetRechargeBillSummary(ctx context.Context, in *GetRechargeBillSummaryRequest, opts ...grpc.CallOption) (*GetRechargeBillSummaryResponse, error)
RefreshGooglePaymentPrices(ctx context.Context, in *RefreshGooglePaymentPricesRequest, opts ...grpc.CallOption) (*RefreshGooglePaymentPricesResponse, error)
GetWalletOverview(ctx context.Context, in *GetWalletOverviewRequest, opts ...grpc.CallOption) (*GetWalletOverviewResponse, error) GetWalletOverview(ctx context.Context, in *GetWalletOverviewRequest, opts ...grpc.CallOption) (*GetWalletOverviewResponse, error)
GetWalletValueSummary(ctx context.Context, in *GetWalletValueSummaryRequest, opts ...grpc.CallOption) (*GetWalletValueSummaryResponse, error) GetWalletValueSummary(ctx context.Context, in *GetWalletValueSummaryRequest, opts ...grpc.CallOption) (*GetWalletValueSummaryResponse, error)
GetUserGiftWall(ctx context.Context, in *GetUserGiftWallRequest, opts ...grpc.CallOption) (*GetUserGiftWallResponse, error) GetUserGiftWall(ctx context.Context, in *GetUserGiftWallRequest, opts ...grpc.CallOption) (*GetUserGiftWallResponse, error)
@ -877,6 +881,26 @@ func (c *walletServiceClient) ListRechargeBills(ctx context.Context, in *ListRec
return out, nil return out, nil
} }
func (c *walletServiceClient) GetRechargeBillSummary(ctx context.Context, in *GetRechargeBillSummaryRequest, opts ...grpc.CallOption) (*GetRechargeBillSummaryResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetRechargeBillSummaryResponse)
err := c.cc.Invoke(ctx, WalletService_GetRechargeBillSummary_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) RefreshGooglePaymentPrices(ctx context.Context, in *RefreshGooglePaymentPricesRequest, opts ...grpc.CallOption) (*RefreshGooglePaymentPricesResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RefreshGooglePaymentPricesResponse)
err := c.cc.Invoke(ctx, WalletService_RefreshGooglePaymentPrices_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *walletServiceClient) GetWalletOverview(ctx context.Context, in *GetWalletOverviewRequest, opts ...grpc.CallOption) (*GetWalletOverviewResponse, error) { func (c *walletServiceClient) GetWalletOverview(ctx context.Context, in *GetWalletOverviewRequest, opts ...grpc.CallOption) (*GetWalletOverviewResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetWalletOverviewResponse) out := new(GetWalletOverviewResponse)
@ -1400,6 +1424,8 @@ type WalletServiceServer interface {
ListResourceShopPurchaseOrders(context.Context, *ListResourceShopPurchaseOrdersRequest) (*ListResourceShopPurchaseOrdersResponse, error) ListResourceShopPurchaseOrders(context.Context, *ListResourceShopPurchaseOrdersRequest) (*ListResourceShopPurchaseOrdersResponse, error)
PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error) PurchaseResourceShopItem(context.Context, *PurchaseResourceShopItemRequest) (*PurchaseResourceShopItemResponse, error)
ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error)
GetRechargeBillSummary(context.Context, *GetRechargeBillSummaryRequest) (*GetRechargeBillSummaryResponse, error)
RefreshGooglePaymentPrices(context.Context, *RefreshGooglePaymentPricesRequest) (*RefreshGooglePaymentPricesResponse, error)
GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error)
GetWalletValueSummary(context.Context, *GetWalletValueSummaryRequest) (*GetWalletValueSummaryResponse, error) GetWalletValueSummary(context.Context, *GetWalletValueSummaryRequest) (*GetWalletValueSummaryResponse, error)
GetUserGiftWall(context.Context, *GetUserGiftWallRequest) (*GetUserGiftWallResponse, error) GetUserGiftWall(context.Context, *GetUserGiftWallRequest) (*GetUserGiftWallResponse, error)
@ -1598,6 +1624,12 @@ func (UnimplementedWalletServiceServer) PurchaseResourceShopItem(context.Context
func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) { func (UnimplementedWalletServiceServer) ListRechargeBills(context.Context, *ListRechargeBillsRequest) (*ListRechargeBillsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRechargeBills not implemented") return nil, status.Errorf(codes.Unimplemented, "method ListRechargeBills not implemented")
} }
func (UnimplementedWalletServiceServer) GetRechargeBillSummary(context.Context, *GetRechargeBillSummaryRequest) (*GetRechargeBillSummaryResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRechargeBillSummary not implemented")
}
func (UnimplementedWalletServiceServer) RefreshGooglePaymentPrices(context.Context, *RefreshGooglePaymentPricesRequest) (*RefreshGooglePaymentPricesResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method RefreshGooglePaymentPrices not implemented")
}
func (UnimplementedWalletServiceServer) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) { func (UnimplementedWalletServiceServer) GetWalletOverview(context.Context, *GetWalletOverviewRequest) (*GetWalletOverviewResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetWalletOverview not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetWalletOverview not implemented")
} }
@ -2606,6 +2638,42 @@ func _WalletService_ListRechargeBills_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _WalletService_GetRechargeBillSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetRechargeBillSummaryRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).GetRechargeBillSummary(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_GetRechargeBillSummary_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).GetRechargeBillSummary(ctx, req.(*GetRechargeBillSummaryRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_RefreshGooglePaymentPrices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RefreshGooglePaymentPricesRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(WalletServiceServer).RefreshGooglePaymentPrices(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: WalletService_RefreshGooglePaymentPrices_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(WalletServiceServer).RefreshGooglePaymentPrices(ctx, req.(*RefreshGooglePaymentPricesRequest))
}
return interceptor(ctx, in, info, handler)
}
func _WalletService_GetWalletOverview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _WalletService_GetWalletOverview_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetWalletOverviewRequest) in := new(GetWalletOverviewRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -3647,6 +3715,14 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListRechargeBills", MethodName: "ListRechargeBills",
Handler: _WalletService_ListRechargeBills_Handler, Handler: _WalletService_ListRechargeBills_Handler,
}, },
{
MethodName: "GetRechargeBillSummary",
Handler: _WalletService_GetRechargeBillSummary_Handler,
},
{
MethodName: "RefreshGooglePaymentPrices",
Handler: _WalletService_RefreshGooglePaymentPrices_Handler,
},
{ {
MethodName: "GetWalletOverview", MethodName: "GetWalletOverview",
Handler: _WalletService_GetWalletOverview_Handler, Handler: _WalletService_GetWalletOverview_Handler,

View File

@ -0,0 +1,12 @@
-- 财务系统 APP 充值详情payment_orders 补 Google Play Orders API 实付明细列。
-- 与 wallet-service 启动迁移 ensureGooglePaymentPaidDetailsSchema 等价,供手工升级线上库使用。
ALTER TABLE payment_orders
ADD COLUMN paid_currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '用户实付币种,来自 Google Orders API' AFTER amount_micro,
ADD COLUMN paid_amount_micro BIGINT NOT NULL DEFAULT 0 COMMENT '用户实付总额微单位(实付币种)' AFTER paid_currency_code,
ADD COLUMN paid_tax_micro BIGINT NOT NULL DEFAULT 0 COMMENT '订单税额微单位(实付币种)' AFTER paid_amount_micro,
ADD COLUMN paid_net_micro BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 扣费后开发者净收入微单位(实付币种)' AFTER paid_tax_micro,
ADD COLUMN paid_synced_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实付明细同步时间UTC epoch ms0 表示未同步' AFTER paid_net_micro;
ALTER TABLE payment_orders
ADD INDEX idx_payment_orders_wallet_tx (app_code, wallet_transaction_id);

View File

@ -41,6 +41,8 @@ type Client interface {
SettleSalaryWithdrawal(ctx context.Context, req *walletv1.SettleSalaryWithdrawalRequest) (*walletv1.SettleSalaryWithdrawalResponse, error) SettleSalaryWithdrawal(ctx context.Context, req *walletv1.SettleSalaryWithdrawalRequest) (*walletv1.SettleSalaryWithdrawalResponse, error)
ReleaseSalaryWithdrawal(ctx context.Context, req *walletv1.ReleaseSalaryWithdrawalRequest) (*walletv1.ReleaseSalaryWithdrawalResponse, error) ReleaseSalaryWithdrawal(ctx context.Context, req *walletv1.ReleaseSalaryWithdrawalRequest) (*walletv1.ReleaseSalaryWithdrawalResponse, error)
ListRechargeBills(ctx context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error) ListRechargeBills(ctx context.Context, req *walletv1.ListRechargeBillsRequest) (*walletv1.ListRechargeBillsResponse, error)
GetRechargeBillSummary(ctx context.Context, req *walletv1.GetRechargeBillSummaryRequest) (*walletv1.GetRechargeBillSummaryResponse, error)
RefreshGooglePaymentPrices(ctx context.Context, req *walletv1.RefreshGooglePaymentPricesRequest) (*walletv1.RefreshGooglePaymentPricesResponse, error)
ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error)
CreateTemporaryRechargeOrder(ctx context.Context, req *walletv1.CreateTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) CreateTemporaryRechargeOrder(ctx context.Context, req *walletv1.CreateTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error) GetTemporaryRechargeOrder(ctx context.Context, req *walletv1.GetTemporaryRechargeOrderRequest) (*walletv1.H5RechargeOrderResponse, error)
@ -194,6 +196,14 @@ func (c *GRPCClient) ListRechargeBills(ctx context.Context, req *walletv1.ListRe
return c.client.ListRechargeBills(ctx, req) return c.client.ListRechargeBills(ctx, req)
} }
func (c *GRPCClient) GetRechargeBillSummary(ctx context.Context, req *walletv1.GetRechargeBillSummaryRequest) (*walletv1.GetRechargeBillSummaryResponse, error) {
return c.client.GetRechargeBillSummary(ctx, req)
}
func (c *GRPCClient) RefreshGooglePaymentPrices(ctx context.Context, req *walletv1.RefreshGooglePaymentPricesRequest) (*walletv1.RefreshGooglePaymentPricesResponse, error) {
return c.client.RefreshGooglePaymentPrices(ctx, req)
}
func (c *GRPCClient) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) { func (c *GRPCClient) ListThirdPartyPaymentChannels(ctx context.Context, req *walletv1.ListThirdPartyPaymentChannelsRequest) (*walletv1.ListThirdPartyPaymentChannelsResponse, error) {
return c.client.ListThirdPartyPaymentChannels(ctx, req) return c.client.ListThirdPartyPaymentChannels(ctx, req)
} }

View File

@ -28,6 +28,13 @@ type rechargeBillDTO struct {
ExchangeUSDMinorAmount int64 `json:"exchangeUsdMinorAmount"` ExchangeUSDMinorAmount int64 `json:"exchangeUsdMinorAmount"`
ProviderAmountMinor int64 `json:"providerAmountMinor,omitempty"` ProviderAmountMinor int64 `json:"providerAmountMinor,omitempty"`
CreatedAtMS int64 `json:"createdAtMs"` CreatedAtMS int64 `json:"createdAtMs"`
ProviderCode string `json:"providerCode"`
UserPaidCurrencyCode string `json:"userPaidCurrencyCode"`
UserPaidAmountMicro int64 `json:"userPaidAmountMicro"`
ProviderFeeMicro int64 `json:"providerFeeMicro"`
ProviderTaxMicro int64 `json:"providerTaxMicro"`
ProviderNetMicro int64 `json:"providerNetMicro"`
PaidSyncedAtMS int64 `json:"paidSyncedAtMs"`
User rechargeBillUserDTO `json:"user"` User rechargeBillUserDTO `json:"user"`
Seller rechargeBillUserDTO `json:"seller"` Seller rechargeBillUserDTO `json:"seller"`
} }
@ -168,6 +175,13 @@ func rechargeBillFromProto(item *walletv1.RechargeBill) rechargeBillDTO {
ExchangeUSDMinorAmount: item.GetExchangeUsdMinorAmount(), ExchangeUSDMinorAmount: item.GetExchangeUsdMinorAmount(),
ProviderAmountMinor: item.GetProviderAmountMinor(), ProviderAmountMinor: item.GetProviderAmountMinor(),
CreatedAtMS: item.GetCreatedAtMs(), CreatedAtMS: item.GetCreatedAtMs(),
ProviderCode: item.GetProviderCode(),
UserPaidCurrencyCode: item.GetUserPaidCurrencyCode(),
UserPaidAmountMicro: item.GetUserPaidAmountMicro(),
ProviderFeeMicro: item.GetProviderFeeMicro(),
ProviderTaxMicro: item.GetProviderTaxMicro(),
ProviderNetMicro: item.GetProviderNetMicro(),
PaidSyncedAtMS: item.GetPaidSyncedAtMs(),
} }
} }

View File

@ -0,0 +1,196 @@
package payment
import (
"context"
"strings"
"time"
"hyapp-admin-server/internal/appctx"
"hyapp-admin-server/internal/middleware"
"hyapp-admin-server/internal/modules/shared"
"hyapp-admin-server/internal/repository"
"hyapp-admin-server/internal/response"
walletv1 "hyapp.local/api/proto/wallet/v1"
"github.com/gin-gonic/gin"
)
type rechargeBillSummaryBucketDTO struct {
BillCount int64 `json:"billCount"`
CoinAmount int64 `json:"coinAmount"`
USDMinorAmount int64 `json:"usdMinorAmount"`
}
type rechargeBillSummaryDTO struct {
Total rechargeBillSummaryBucketDTO `json:"total"`
GooglePlay rechargeBillSummaryBucketDTO `json:"googlePlay"`
ThirdParty rechargeBillSummaryBucketDTO `json:"thirdParty"`
}
type rechargeBillRegionDTO struct {
RegionID int64 `json:"regionId"`
RegionCode string `json:"regionCode"`
Name string `json:"name"`
}
type googleRechargePaidRefreshRequest struct {
TransactionIDs []string `json:"transactionIds"`
}
type googleRechargePaidDTO struct {
TransactionID string `json:"transactionId"`
CurrencyCode string `json:"currencyCode"`
PaidAmountMicro int64 `json:"paidAmountMicro"`
TaxMicro int64 `json:"taxMicro"`
NetMicro int64 `json:"netMicro"`
SyncedAtMS int64 `json:"syncedAtMs"`
Error string `json:"error,omitempty"`
}
// GetRechargeBillSummary 返回与账单列表同一筛选口径的充值总和、Google 充值与三方充值聚合。
func (h *Handler) GetRechargeBillSummary(c *gin.Context) {
options := shared.ListOptions(c)
appCode := appctx.FromContext(c.Request.Context())
userFilter := shared.UserIdentityFilterFromQuery(c, "")
userID, userMatched, ok := h.resolveRechargeBillUserFilter(c, appCode, userFilter, "user_id 参数不正确", "user_id", "userId")
if !ok {
return
}
sellerFilter := shared.UserIdentityFilterFromQuery(c, "seller")
sellerUserID, sellerMatched, ok := h.resolveRechargeBillUserFilter(c, appCode, sellerFilter, "seller_user_id 参数不正确", "seller_user_id", "sellerUserId")
if !ok {
return
}
if (!userFilter.IsEmpty() && !userMatched) || (!sellerFilter.IsEmpty() && !sellerMatched) {
response.OK(c, rechargeBillSummaryDTO{})
return
}
resp, err := h.wallet.GetRechargeBillSummary(c.Request.Context(), &walletv1.GetRechargeBillSummaryRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appCode,
UserId: userID,
SellerUserId: sellerUserID,
RegionId: queryInt64(c, "region_id", "regionId"),
RechargeType: strings.TrimSpace(firstQuery(c, "recharge_type", "rechargeType")),
Status: options.Status,
Keyword: options.Keyword,
StartAtMs: queryInt64(c, "start_at_ms", "startAtMs"),
EndAtMs: queryInt64(c, "end_at_ms", "endAtMs"),
})
if err != nil {
writeWalletError(c, err, "获取充值汇总失败")
return
}
response.OK(c, rechargeBillSummaryDTO{
Total: rechargeBillSummaryBucketFromProto(resp.GetTotal()),
GooglePlay: rechargeBillSummaryBucketFromProto(resp.GetGooglePlay()),
ThirdParty: rechargeBillSummaryBucketFromProto(resp.GetThirdParty()),
})
}
func rechargeBillSummaryBucketFromProto(bucket *walletv1.RechargeBillSummaryBucket) rechargeBillSummaryBucketDTO {
if bucket == nil {
return rechargeBillSummaryBucketDTO{}
}
return rechargeBillSummaryBucketDTO{
BillCount: bucket.GetBillCount(),
CoinAmount: bucket.GetCoinAmount(),
USDMinorAmount: bucket.GetUsdMinorAmount(),
}
}
// RefreshGoogleRechargePaidDetails 对指定账单触发 Google Orders API 实付明细同步,供财务补齐历史订单实付。
func (h *Handler) RefreshGoogleRechargePaidDetails(c *gin.Context) {
var request googleRechargePaidRefreshRequest
if err := c.ShouldBindJSON(&request); err != nil || len(request.TransactionIDs) == 0 {
response.BadRequest(c, "交易号参数不正确")
return
}
if len(request.TransactionIDs) > 100 {
response.BadRequest(c, "单次最多刷新 100 笔账单")
return
}
appCode := appctx.FromContext(c.Request.Context())
resp, err := h.wallet.RefreshGooglePaymentPrices(c.Request.Context(), &walletv1.RefreshGooglePaymentPricesRequest{
RequestId: middleware.CurrentRequestID(c),
AppCode: appCode,
TransactionIds: request.TransactionIDs,
})
if err != nil {
writeWalletError(c, err, "查询谷歌实付明细失败")
return
}
items := make([]googleRechargePaidDTO, 0, len(resp.GetPrices()))
for _, price := range resp.GetPrices() {
items = append(items, googleRechargePaidDTO{
TransactionID: price.GetTransactionId(),
CurrencyCode: price.GetCurrencyCode(),
PaidAmountMicro: price.GetPaidAmountMicro(),
TaxMicro: price.GetTaxMicro(),
NetMicro: price.GetNetMicro(),
SyncedAtMS: price.GetSyncedAtMs(),
Error: price.GetError(),
})
}
shared.OperationLog(c, h.audit, "refresh-google-recharge-paid", "payment_orders", "success", appCode)
response.OK(c, gin.H{"items": items})
}
// ListRechargeBillRegions 返回当前 App 的区域目录,用于充值详情按区域筛选;不受财务范围授权限制。
func (h *Handler) ListRechargeBillRegions(c *gin.Context) {
appCode := appctx.FromContext(c.Request.Context())
regions, err := h.listRechargeBillRegions(c.Request.Context(), appCode)
if err != nil {
response.ServerError(c, "获取区域列表失败")
return
}
response.OK(c, gin.H{"items": regions, "total": len(regions)})
}
func (h *Handler) listRechargeBillRegions(ctx context.Context, appCode string) ([]rechargeBillRegionDTO, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
items := []rechargeBillRegionDTO{}
seen := map[int64]struct{}{}
if h.userDB != nil {
rows, err := h.userDB.QueryContext(ctx, `
SELECT region_id, region_code, name
FROM regions
WHERE app_code = ? AND status = 'active'
ORDER BY sort_order ASC, name ASC`, appCode)
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var item rechargeBillRegionDTO
if err := rows.Scan(&item.RegionID, &item.RegionCode, &item.Name); err != nil {
return nil, err
}
seen[item.RegionID] = struct{}{}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, err
}
}
// Yumi/Aslan 这类 legacy App 的区域目录在线上 likei Mongo这里只取目录展示账单筛选仍以 wallet 的 target_region_id 为准。
for _, source := range h.moneyRegionSources {
_, sourceRegions, _, err := source.ListMoneyMasterData(ctx, repository.MoneyAccess{All: true})
if err != nil {
return nil, err
}
for _, region := range sourceRegions {
if appctx.Normalize(region.AppCode) != appCode {
continue
}
if _, ok := seen[region.RegionID]; ok {
continue
}
seen[region.RegionID] = struct{}{}
items = append(items, rechargeBillRegionDTO{RegionID: region.RegionID, RegionCode: region.RegionCode, Name: region.Name})
}
}
return items, nil
}

View File

@ -12,6 +12,9 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) {
} }
protected.GET("/admin/payment/recharge-bills", middleware.RequirePermission("payment-bill:view"), h.ListRechargeBills) protected.GET("/admin/payment/recharge-bills", middleware.RequirePermission("payment-bill:view"), h.ListRechargeBills)
protected.GET("/admin/payment/recharge-bills/summary", middleware.RequirePermission("payment-bill:view"), h.GetRechargeBillSummary)
protected.POST("/admin/payment/recharge-bills/google-paid/refresh", middleware.RequirePermission("payment-bill:view"), h.RefreshGoogleRechargePaidDetails)
protected.GET("/admin/payment/recharge-regions", middleware.RequirePermission("payment-bill:view"), h.ListRechargeBillRegions)
protected.GET("/admin/payment/third-party-channels", middleware.RequirePermission("payment-third-party:view"), h.ListThirdPartyPaymentChannels) protected.GET("/admin/payment/third-party-channels", middleware.RequirePermission("payment-third-party:view"), h.ListThirdPartyPaymentChannels)
protected.GET("/admin/finance/scope", middleware.RequirePermission(financeViewPermission), h.GetMoneyScope) protected.GET("/admin/finance/scope", middleware.RequirePermission(financeViewPermission), h.GetMoneyScope)
protected.GET("/admin/money/scope", middleware.RequireAnyPermission(financeViewPermission, legacyMoneyViewPermission), h.GetMoneyScope) protected.GET("/admin/money/scope", middleware.RequireAnyPermission(financeViewPermission, legacyMoneyViewPermission), h.GetMoneyScope)

View File

@ -836,6 +836,11 @@ CREATE TABLE IF NOT EXISTS payment_orders (
coin_amount BIGINT NOT NULL COMMENT '到账金币数量', coin_amount BIGINT NOT NULL COMMENT '到账金币数量',
currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '商品币种编码', currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '商品币种编码',
amount_micro BIGINT NOT NULL DEFAULT 0 COMMENT '商品金额微单位', amount_micro BIGINT NOT NULL DEFAULT 0 COMMENT '商品金额微单位',
paid_currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '用户实付币种,来自 Google Orders API',
paid_amount_micro BIGINT NOT NULL DEFAULT 0 COMMENT '用户实付总额微单位(实付币种)',
paid_tax_micro BIGINT NOT NULL DEFAULT 0 COMMENT '订单税额微单位(实付币种)',
paid_net_micro BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 扣费后开发者净收入微单位(实付币种)',
paid_synced_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实付明细同步时间UTC epoch ms0 表示未同步',
provider_payload JSON NULL COMMENT 'Google Play 校验返回快照', provider_payload JSON NULL COMMENT 'Google Play 校验返回快照',
created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms', created_at_ms BIGINT NOT NULL COMMENT '创建时间UTC epoch ms',
updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间UTC epoch ms',
@ -843,7 +848,8 @@ CREATE TABLE IF NOT EXISTS payment_orders (
UNIQUE KEY uk_payment_orders_token (app_code, provider, purchase_token_hash), UNIQUE KEY uk_payment_orders_token (app_code, provider, purchase_token_hash),
UNIQUE KEY uk_payment_orders_command (app_code, command_id), UNIQUE KEY uk_payment_orders_command (app_code, command_id),
KEY idx_payment_orders_user_time (app_code, user_id, created_at_ms), KEY idx_payment_orders_user_time (app_code, user_id, created_at_ms),
KEY idx_payment_orders_provider_order (app_code, provider, provider_order_id) KEY idx_payment_orders_provider_order (app_code, provider, provider_order_id),
KEY idx_payment_orders_wallet_tx (app_code, wallet_transaction_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付订单与 provider 审计表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='支付订单与 provider 审计表';
CREATE TABLE IF NOT EXISTS coin_seller_stock_records ( CREATE TABLE IF NOT EXISTS coin_seller_stock_records (

View File

@ -131,6 +131,46 @@ func (c *Client) GetProductPurchase(ctx context.Context, packageName string, pur
return purchase, nil return purchase, nil
} }
// GetOrder 拉取 Play Developer API Orders 资源,取回用户实付币种、实付总额、税额和开发者净收入。
// 需要服务账号在 Play Console 拥有“查看财务数据”权限,否则 Google 返回 403。
func (c *Client) GetOrder(ctx context.Context, packageName string, orderID string) (ledger.GooglePlayOrder, error) {
if c == nil {
return ledger.GooglePlayOrder{}, xerr.New(xerr.Unavailable, "google play client is not configured")
}
packageName = c.normalizePackageName(packageName)
orderID = strings.TrimSpace(orderID)
if packageName == "" || orderID == "" {
return ledger.GooglePlayOrder{}, xerr.New(xerr.InvalidArgument, "google play order request is incomplete")
}
endpoint := fmt.Sprintf("%s/androidpublisher/v3/applications/%s/orders/%s",
c.apiBaseURL,
url.PathEscape(packageName),
url.PathEscape(orderID),
)
body, err := c.doJSON(ctx, http.MethodGet, endpoint, nil)
if err != nil {
return ledger.GooglePlayOrder{}, err
}
var parsed orderResource
if err := json.Unmarshal(body, &parsed); err != nil {
return ledger.GooglePlayOrder{}, err
}
order := ledger.GooglePlayOrder{
OrderID: parsed.OrderID,
State: parsed.State,
BuyerCountry: parsed.BuyerAddress.BuyerCountry,
CurrencyCode: parsed.Total.CurrencyCode,
TotalAmountMicro: parsed.Total.micro(),
TaxAmountMicro: parsed.Tax.micro(),
NetAmountMicro: parsed.DeveloperRevenueInBuyerCurrency.micro(),
RawJSON: string(body),
}
if order.CurrencyCode == "" {
order.CurrencyCode = parsed.Tax.CurrencyCode
}
return order, nil
}
func (c *Client) ConsumeProduct(ctx context.Context, packageName string, productID string, purchaseToken string) error { func (c *Client) ConsumeProduct(ctx context.Context, packageName string, productID string, purchaseToken string) error {
if c == nil { if c == nil {
return xerr.New(xerr.Unavailable, "google play client is not configured") return xerr.New(xerr.Unavailable, "google play client is not configured")
@ -279,3 +319,28 @@ type productLineItem struct {
type productOfferDetails struct { type productOfferDetails struct {
ConsumptionState string `json:"consumptionState"` ConsumptionState string `json:"consumptionState"`
} }
type orderResource struct {
OrderID string `json:"orderId"`
State string `json:"state"`
BuyerAddress buyerAddress `json:"buyerAddress"`
Total moneyAmount `json:"total"`
Tax moneyAmount `json:"tax"`
DeveloperRevenueInBuyerCurrency moneyAmount `json:"developerRevenueInBuyerCurrency"`
}
type buyerAddress struct {
BuyerCountry string `json:"buyerCountry"`
}
// moneyAmount 是 google.type.Money 的 JSON 形态units 是字符串形式的整数金额nanos 是 10^-9 小数部分。
type moneyAmount struct {
CurrencyCode string `json:"currencyCode"`
Units json.Number `json:"units"`
Nanos int64 `json:"nanos"`
}
func (m moneyAmount) micro() int64 {
units, _ := m.Units.Int64()
return units*1_000_000 + m.Nanos/1_000
}

View File

@ -84,6 +84,37 @@ type GooglePlayPurchase struct {
RawJSON string RawJSON string
} }
// GooglePlayOrder 是 Play Developer API Orders 接口返回的订单实付快照;金额统一收敛为实付币种微单位。
type GooglePlayOrder struct {
OrderID string
State string
BuyerCountry string
CurrencyCode string
TotalAmountMicro int64
TaxAmountMicro int64
// NetAmountMicro 是 Google 扣除服务费和代缴税后的开发者净收入(实付币种);接口未返回时为 0。
NetAmountMicro int64
RawJSON string
}
// GooglePaymentPaidDetails 是写回 payment_orders 的实付明细命令。
type GooglePaymentPaidDetails struct {
CurrencyCode string
PaidAmountMicro int64
TaxAmountMicro int64
NetAmountMicro int64
SyncedAtMS int64
}
// GooglePaymentOrderRef 是按钱包交易反查 Google 支付订单、发起 Orders API 查询所需的最小定位信息。
type GooglePaymentOrderRef struct {
PaymentOrderID string
TransactionID string
PackageName string
ProviderOrderID string
PaidSyncedAtMS int64
}
// GooglePaymentReceipt 是 Google 支付确认接口返回给 App 的稳定入账回执。 // GooglePaymentReceipt 是 Google 支付确认接口返回给 App 的稳定入账回执。
type GooglePaymentReceipt struct { type GooglePaymentReceipt struct {
PaymentOrderID string PaymentOrderID string

View File

@ -25,6 +25,31 @@ type RechargeBill struct {
ExchangeUSDMinorAmount int64 ExchangeUSDMinorAmount int64
ProviderAmountMinor int64 ProviderAmountMinor int64
CreatedAtMS int64 CreatedAtMS int64
// ProviderCode 标识实际支付渠道google_play/mifapay/v5pay/usdt_trc20。
ProviderCode string
// UserPaid* 是用户实付事实:外部订单来自三方下单/回调快照Google 订单来自 Play Orders API。
UserPaidCurrencyCode string
UserPaidAmountMicro int64
// ProviderFee/Tax/Net 是三方扣款事实实付币种微单位0 表示渠道未提供。
ProviderFeeMicro int64
ProviderTaxMicro int64
ProviderNetMicro int64
// PaidSyncedAtMS 仅对 Google 账单有意义Orders API 实付明细的同步时间0 表示未同步。
PaidSyncedAtMS int64
}
// RechargeBillSummaryBucket 是充值账单的聚合桶:笔数、金币总数与美元最小单位总额。
type RechargeBillSummaryBucket struct {
BillCount int64
CoinAmount int64
USDMinorAmount int64
}
// RechargeBillSummary 拆分财务系统关心的三个口径总和、Google 充值与三方充值。
type RechargeBillSummary struct {
Total RechargeBillSummaryBucket
GooglePlay RechargeBillSummaryBucket
ThirdParty RechargeBillSummaryBucket
} }
// ListRechargeBillsQuery 是后台查询所有充值渠道账单的筛选条件。 // ListRechargeBillsQuery 是后台查询所有充值渠道账单的筛选条件。

View File

@ -13,10 +13,11 @@ type ActivityBadgeClient interface {
ConsumeAchievementEvent(ctx context.Context, req *activityv1.ConsumeAchievementEventRequest, opts ...grpc.CallOption) (*activityv1.ConsumeAchievementEventResponse, error) ConsumeAchievementEvent(ctx context.Context, req *activityv1.ConsumeAchievementEventRequest, opts ...grpc.CallOption) (*activityv1.ConsumeAchievementEventResponse, error)
} }
// GooglePlayClient 隔离 Google Play Developer APIservice 只依赖购买校验和消耗能力。 // GooglePlayClient 隔离 Google Play Developer APIservice 只依赖购买校验、消耗和订单实付查询能力。
type GooglePlayClient interface { type GooglePlayClient interface {
GetProductPurchase(ctx context.Context, packageName string, purchaseToken string) (ledger.GooglePlayPurchase, error) GetProductPurchase(ctx context.Context, packageName string, purchaseToken string) (ledger.GooglePlayPurchase, error)
ConsumeProduct(ctx context.Context, packageName string, productID string, purchaseToken string) error ConsumeProduct(ctx context.Context, packageName string, productID string, purchaseToken string) error
GetOrder(ctx context.Context, packageName string, orderID string) (ledger.GooglePlayOrder, error)
} }
// MifaPayCreateOrderRequest 是 wallet-service 传给 MiFaPay client 的已校验下单快照。 // MifaPayCreateOrderRequest 是 wallet-service 传给 MiFaPay client 的已校验下单快照。

View File

@ -94,6 +94,9 @@ type GameLedgerStore interface {
// RechargeStore 管理内购充值档位、Google 支付和充值账单。 // RechargeStore 管理内购充值档位、Google 支付和充值账单。
type RechargeStore interface { type RechargeStore interface {
ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error)
SummarizeRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) (ledger.RechargeBillSummary, error)
ListGooglePaymentOrderRefs(ctx context.Context, appCode string, transactionIDs []string) (map[string]ledger.GooglePaymentOrderRef, error)
UpdateGooglePaymentPaidDetails(ctx context.Context, appCode string, paymentOrderID string, details ledger.GooglePaymentPaidDetails) error
ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error)
ListAdminRechargeProducts(ctx context.Context, query ledger.ListRechargeProductsQuery) ([]ledger.RechargeProduct, int64, error) ListAdminRechargeProducts(ctx context.Context, query ledger.ListRechargeProductsQuery) ([]ledger.RechargeProduct, int64, error)
GetRechargeProduct(ctx context.Context, appCode string, productID int64) (ledger.RechargeProduct, error) GetRechargeProduct(ctx context.Context, appCode string, productID int64) (ledger.RechargeProduct, error)

View File

@ -6,6 +6,7 @@ import (
"hyapp/pkg/xerr" "hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/domain/ledger" "hyapp/services/wallet-service/internal/domain/ledger"
"strings" "strings"
"time"
) )
// ListRechargeBills 返回充值账单只读列表;所有充值来源必须先在 wallet-service 落充值记录。 // ListRechargeBills 返回充值账单只读列表;所有充值来源必须先在 wallet-service 落充值记录。
@ -19,6 +20,97 @@ func (s *Service) ListRechargeBills(ctx context.Context, query ledger.ListRechar
return s.repository.ListRechargeBills(ctx, query) return s.repository.ListRechargeBills(ctx, query)
} }
// GetRechargeBillSummary 按与账单列表一致的筛选口径聚合充值总和、Google 充值与三方充值。
func (s *Service) GetRechargeBillSummary(ctx context.Context, query ledger.ListRechargeBillsQuery) (ledger.RechargeBillSummary, error) {
if s.repository == nil {
return ledger.RechargeBillSummary{}, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
query.AppCode = appcode.Normalize(query.AppCode)
ctx = appcode.WithContext(ctx, query.AppCode)
return s.repository.SummarizeRechargeBills(ctx, query)
}
// GooglePaymentPriceResult 是单笔 Google 账单实付明细刷新结果Err 非空表示该笔刷新失败但不影响其他账单。
type GooglePaymentPriceResult struct {
TransactionID string
CurrencyCode string
PaidAmountMicro int64
TaxAmountMicro int64
NetAmountMicro int64
SyncedAtMS int64
Err string
}
const maxGooglePaymentPriceRefreshBatch = 100
// RefreshGooglePaymentPrices 用 Play Orders API 拉取账单的用户实付币种、总额、税额和净收入并写回 payment_orders。
// 服务账号需要 Play Console 的“查看财务数据”权限;单笔失败只标记该笔的 Err整批继续。
func (s *Service) RefreshGooglePaymentPrices(ctx context.Context, appCode string, transactionIDs []string) ([]GooglePaymentPriceResult, error) {
if s.repository == nil {
return nil, xerr.New(xerr.Unavailable, "wallet repository is not configured")
}
if s.googlePlay == nil {
return nil, xerr.New(xerr.Unavailable, "google play client is not configured")
}
if len(transactionIDs) == 0 {
return []GooglePaymentPriceResult{}, nil
}
if len(transactionIDs) > maxGooglePaymentPriceRefreshBatch {
return nil, xerr.New(xerr.InvalidArgument, "too many transaction_ids")
}
appCode = appcode.Normalize(appCode)
ctx = appcode.WithContext(ctx, appCode)
refs, err := s.repository.ListGooglePaymentOrderRefs(ctx, appCode, transactionIDs)
if err != nil {
return nil, err
}
results := make([]GooglePaymentPriceResult, 0, len(transactionIDs))
for _, transactionID := range transactionIDs {
transactionID = strings.TrimSpace(transactionID)
if transactionID == "" {
continue
}
ref, ok := refs[transactionID]
if !ok {
results = append(results, GooglePaymentPriceResult{TransactionID: transactionID, Err: "not a google payment bill"})
continue
}
result := s.refreshGooglePaymentPrice(ctx, appCode, ref)
results = append(results, result)
}
return results, nil
}
func (s *Service) refreshGooglePaymentPrice(ctx context.Context, appCode string, ref ledger.GooglePaymentOrderRef) GooglePaymentPriceResult {
result := GooglePaymentPriceResult{TransactionID: ref.TransactionID}
if strings.TrimSpace(ref.ProviderOrderID) == "" {
result.Err = "google order id is missing"
return result
}
order, err := s.googlePlay.GetOrder(ctx, ref.PackageName, ref.ProviderOrderID)
if err != nil {
result.Err = err.Error()
return result
}
details := ledger.GooglePaymentPaidDetails{
CurrencyCode: order.CurrencyCode,
PaidAmountMicro: order.TotalAmountMicro,
TaxAmountMicro: order.TaxAmountMicro,
NetAmountMicro: order.NetAmountMicro,
SyncedAtMS: s.now().UnixMilli(),
}
if err := s.repository.UpdateGooglePaymentPaidDetails(ctx, appCode, ref.PaymentOrderID, details); err != nil {
result.Err = err.Error()
return result
}
result.CurrencyCode = details.CurrencyCode
result.PaidAmountMicro = details.PaidAmountMicro
result.TaxAmountMicro = details.TaxAmountMicro
result.NetAmountMicro = details.NetAmountMicro
result.SyncedAtMS = details.SyncedAtMS
return result
}
// ListRechargeProducts 返回区域化充值档位region_id 必须由 gateway 从 user-service 资料解析。 // ListRechargeProducts 返回区域化充值档位region_id 必须由 gateway 从 user-service 资料解析。
func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) { func (s *Service) ListRechargeProducts(ctx context.Context, userID int64, regionID int64, platform string, audienceType string) ([]ledger.RechargeProduct, []string, error) {
if userID <= 0 || regionID <= 0 { if userID <= 0 || regionID <= 0 {
@ -172,6 +264,8 @@ func (s *Service) ConfirmGooglePayment(ctx context.Context, command ledger.Googl
if err != nil { if err != nil {
return ledger.GooglePaymentReceipt{}, err return ledger.GooglePaymentReceipt{}, err
} }
// 入账已完成,实付明细只影响财务展示:异步拉 Orders API失败静默后台可手动刷新补数。
s.scheduleGooglePaymentPriceRefresh(command.AppCode, receipt.PaymentOrderID, command.PackageName, purchase.OrderID)
if receipt.ConsumeState == ledger.PaymentConsumeStateConsumed { if receipt.ConsumeState == ledger.PaymentConsumeStateConsumed {
return receipt, nil return receipt, nil
} }
@ -189,6 +283,30 @@ func (s *Service) ConfirmGooglePayment(ctx context.Context, command ledger.Googl
return receipt, nil return receipt, nil
} }
// scheduleGooglePaymentPriceRefresh 在支付确认返回后异步同步实付明细,不给支付主链路增加延迟或失败面。
func (s *Service) scheduleGooglePaymentPriceRefresh(appCode string, paymentOrderID string, packageName string, orderID string) {
if s.googlePlay == nil || s.repository == nil ||
strings.TrimSpace(paymentOrderID) == "" || strings.TrimSpace(orderID) == "" {
return
}
syncedAt := s.now().UnixMilli()
go func() {
ctx, cancel := context.WithTimeout(appcode.WithContext(context.Background(), appCode), 10*time.Second)
defer cancel()
order, err := s.googlePlay.GetOrder(ctx, packageName, orderID)
if err != nil {
return
}
_ = s.repository.UpdateGooglePaymentPaidDetails(ctx, appCode, paymentOrderID, ledger.GooglePaymentPaidDetails{
CurrencyCode: order.CurrencyCode,
PaidAmountMicro: order.TotalAmountMicro,
TaxAmountMicro: order.TaxAmountMicro,
NetAmountMicro: order.NetAmountMicro,
SyncedAtMS: syncedAt,
})
}()
}
func (s *Service) googleRechargeProductForPayment(ctx context.Context, command ledger.GooglePaymentCommand) (ledger.RechargeProduct, error) { func (s *Service) googleRechargeProductForPayment(ctx context.Context, command ledger.GooglePaymentCommand) (ledger.RechargeProduct, error) {
if command.ProductID > 0 { if command.ProductID > 0 {
return s.repository.GetRechargeProduct(ctx, command.AppCode, command.ProductID) return s.repository.GetRechargeProduct(ctx, command.AppCode, command.ProductID)

View File

@ -6192,6 +6192,11 @@ func (f *fakeGooglePlayClient) ConsumeProduct(_ context.Context, _ string, produ
return nil return nil
} }
func (f *fakeGooglePlayClient) GetOrder(_ context.Context, _ string, _ string) (ledger.GooglePlayOrder, error) {
// 实付明细刷新是入账后的异步补数动作fake 直接失败,让确认链路测试不产生额外副作用。
return ledger.GooglePlayOrder{}, xerr.New(xerr.Unavailable, "orders api is not faked")
}
type fakeMifaPayClient struct { type fakeMifaPayClient struct {
createReq walletservice.MifaPayCreateOrderRequest createReq walletservice.MifaPayCreateOrderRequest
createResp walletservice.MifaPayCreateOrderResponse createResp walletservice.MifaPayCreateOrderResponse

View File

@ -299,6 +299,76 @@ func (r *Repository) receiptForGooglePaymentOrder(ctx context.Context, tx *sql.T
return receipt, nil return receipt, nil
} }
// ListGooglePaymentOrderRefs 按钱包交易 ID 批量反查 Google 支付订单定位信息;只返回命中的交易。
func (r *Repository) ListGooglePaymentOrderRefs(ctx context.Context, appCode string, transactionIDs []string) (map[string]ledger.GooglePaymentOrderRef, error) {
if r == nil || r.db == nil {
return nil, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
ctx = contextWithCommandApp(ctx, appCode)
cleaned := make([]string, 0, len(transactionIDs))
for _, transactionID := range transactionIDs {
if transactionID = strings.TrimSpace(transactionID); transactionID != "" {
cleaned = append(cleaned, transactionID)
}
}
if len(cleaned) == 0 {
return map[string]ledger.GooglePaymentOrderRef{}, nil
}
placeholders := strings.TrimSuffix(strings.Repeat("?,", len(cleaned)), ",")
args := make([]any, 0, len(cleaned)+2)
args = append(args, appcode.FromContext(ctx), ledger.PaymentProviderGooglePlay)
for _, transactionID := range cleaned {
args = append(args, transactionID)
}
rows, err := r.db.QueryContext(ctx, `
SELECT payment_order_id, wallet_transaction_id, package_name, provider_order_id, paid_synced_at_ms
FROM payment_orders
WHERE app_code = ? AND provider = ? AND wallet_transaction_id IN (`+placeholders+`)`,
args...,
)
if err != nil {
return nil, err
}
defer rows.Close()
refs := make(map[string]ledger.GooglePaymentOrderRef, len(cleaned))
for rows.Next() {
var ref ledger.GooglePaymentOrderRef
if err := rows.Scan(&ref.PaymentOrderID, &ref.TransactionID, &ref.PackageName, &ref.ProviderOrderID, &ref.PaidSyncedAtMS); err != nil {
return nil, err
}
refs[ref.TransactionID] = ref
}
return refs, rows.Err()
}
// UpdateGooglePaymentPaidDetails 把 Orders API 返回的实付明细写回 payment_orders同步时间必须由调用方给出。
func (r *Repository) UpdateGooglePaymentPaidDetails(ctx context.Context, appCode string, paymentOrderID string, details ledger.GooglePaymentPaidDetails) error {
if r == nil || r.db == nil {
return xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
ctx = contextWithCommandApp(ctx, appCode)
paymentOrderID = strings.TrimSpace(paymentOrderID)
if paymentOrderID == "" {
return xerr.New(xerr.InvalidArgument, "payment_order_id is required")
}
_, err := r.db.ExecContext(ctx, `
UPDATE payment_orders
SET paid_currency_code = ?, paid_amount_micro = ?, paid_tax_micro = ?, paid_net_micro = ?, paid_synced_at_ms = ?, updated_at_ms = ?
WHERE app_code = ? AND payment_order_id = ? AND provider = ?`,
strings.ToUpper(strings.TrimSpace(details.CurrencyCode)),
details.PaidAmountMicro,
details.TaxAmountMicro,
details.NetAmountMicro,
details.SyncedAtMS,
time.Now().UnixMilli(),
appcode.FromContext(ctx),
paymentOrderID,
ledger.PaymentProviderGooglePlay,
)
return err
}
type googlePaymentMetadata struct { type googlePaymentMetadata struct {
AppCode string `json:"app_code"` AppCode string `json:"app_code"`
PaymentOrderID string `json:"payment_order_id"` PaymentOrderID string `json:"payment_order_id"`

View File

@ -2,6 +2,9 @@ package mysql
import ( import (
"context" "context"
"encoding/json"
"math"
"strconv"
"strings" "strings"
"hyapp/pkg/appcode" "hyapp/pkg/appcode"
@ -23,16 +26,21 @@ func (r *Repository) ListRechargeBills(ctx context.Context, query ledger.ListRec
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
// 充值事实仍以 wallet_recharge_records 为主表external_recharge_orders 只补 H5 三方订单的实付金额展示,使用 wallet_transaction_id 索引命中,避免列表查询回扫订单表。 // 充值事实仍以 wallet_recharge_records 为主表external_recharge_orders 补 H5 三方订单的实付币种和金额,
// payment_orders 补 Google 订单经 Orders API 同步的实付明细,两个 LEFT JOIN 都走 wallet_transaction_id 索引。
rows, err := r.db.QueryContext(ctx, ` rows, err := r.db.QueryContext(ctx, `
SELECT rr.app_code, rr.transaction_id, wt.command_id, wt.biz_type, wt.status, wt.external_ref, SELECT rr.app_code, rr.transaction_id, wt.command_id, wt.biz_type, wt.status, wt.external_ref,
rr.user_id, rr.seller_user_id, rr.seller_region_id, rr.target_region_id, rr.user_id, rr.seller_user_id, rr.seller_region_id, rr.target_region_id,
rr.policy_id, rr.policy_version, rr.currency_code, rr.policy_id, rr.policy_version, rr.currency_code,
rr.coin_amount, rr.usd_minor_amount, rr.exchange_coin_amount, rr.exchange_usd_minor_amount, rr.coin_amount, rr.usd_minor_amount, rr.exchange_coin_amount, rr.exchange_usd_minor_amount,
COALESCE(eo.provider_amount_minor, 0), rr.created_at_ms COALESCE(eo.provider_amount_minor, 0), rr.created_at_ms,
COALESCE(eo.provider_code, ''), COALESCE(eo.currency_code, ''), COALESCE(CAST(eo.provider_payload AS CHAR), ''),
COALESCE(po.paid_currency_code, ''), COALESCE(po.paid_amount_micro, 0), COALESCE(po.paid_tax_micro, 0),
COALESCE(po.paid_net_micro, 0), COALESCE(po.paid_synced_at_ms, 0)
FROM wallet_recharge_records rr FROM wallet_recharge_records rr
JOIN wallet_transactions wt ON wt.app_code = rr.app_code AND wt.transaction_id = rr.transaction_id JOIN wallet_transactions wt ON wt.app_code = rr.app_code AND wt.transaction_id = rr.transaction_id
LEFT JOIN external_recharge_orders eo ON eo.app_code = rr.app_code AND eo.wallet_transaction_id = rr.transaction_id LEFT JOIN external_recharge_orders eo ON eo.app_code = rr.app_code AND eo.wallet_transaction_id = rr.transaction_id
LEFT JOIN payment_orders po ON po.app_code = rr.app_code AND po.wallet_transaction_id = rr.transaction_id
`+where+` `+where+`
ORDER BY rr.created_at_ms DESC, rr.transaction_id DESC ORDER BY rr.created_at_ms DESC, rr.transaction_id DESC
LIMIT ? OFFSET ?`, LIMIT ? OFFSET ?`,
@ -46,6 +54,9 @@ func (r *Repository) ListRechargeBills(ctx context.Context, query ledger.ListRec
items := make([]ledger.RechargeBill, 0, query.PageSize) items := make([]ledger.RechargeBill, 0, query.PageSize)
for rows.Next() { for rows.Next() {
var item ledger.RechargeBill var item ledger.RechargeBill
var externalProviderCode, externalCurrencyCode, externalPayloadJSON string
var paidCurrencyCode string
var paidAmountMicro, paidTaxMicro, paidNetMicro, paidSyncedAtMS int64
if err := rows.Scan( if err := rows.Scan(
&item.AppCode, &item.AppCode,
&item.TransactionID, &item.TransactionID,
@ -66,9 +77,19 @@ func (r *Repository) ListRechargeBills(ctx context.Context, query ledger.ListRec
&item.ExchangeUSDMinorAmount, &item.ExchangeUSDMinorAmount,
&item.ProviderAmountMinor, &item.ProviderAmountMinor,
&item.CreatedAtMS, &item.CreatedAtMS,
&externalProviderCode,
&externalCurrencyCode,
&externalPayloadJSON,
&paidCurrencyCode,
&paidAmountMicro,
&paidTaxMicro,
&paidNetMicro,
&paidSyncedAtMS,
); err != nil { ); err != nil {
return nil, 0, err return nil, 0, err
} }
fillRechargeBillPaidFacts(&item, externalProviderCode, externalCurrencyCode, externalPayloadJSON,
paidCurrencyCode, paidAmountMicro, paidTaxMicro, paidNetMicro, paidSyncedAtMS)
items = append(items, item) items = append(items, item)
} }
if err := rows.Err(); err != nil { if err := rows.Err(); err != nil {
@ -77,6 +98,115 @@ func (r *Repository) ListRechargeBills(ctx context.Context, query ledger.ListRec
return items, total, nil return items, total, nil
} }
// fillRechargeBillPaidFacts 把三方订单快照和 Google 实付明细收敛成账单统一的实付字段。
func fillRechargeBillPaidFacts(item *ledger.RechargeBill, externalProviderCode string, externalCurrencyCode string, externalPayloadJSON string,
paidCurrencyCode string, paidAmountMicro int64, paidTaxMicro int64, paidNetMicro int64, paidSyncedAtMS int64) {
if item.RechargeType == bizTypeGooglePlayRecharge {
item.ProviderCode = "google_play"
item.UserPaidCurrencyCode = paidCurrencyCode
item.UserPaidAmountMicro = paidAmountMicro
item.ProviderTaxMicro = paidTaxMicro
item.ProviderNetMicro = paidNetMicro
item.PaidSyncedAtMS = paidSyncedAtMS
// Google 不单独返回服务费,只能由 实付总额 - 净收入 - 税 推出;任一缺失则保持 0。
if paidAmountMicro > 0 && paidNetMicro > 0 {
if fee := paidAmountMicro - paidNetMicro - paidTaxMicro; fee > 0 {
item.ProviderFeeMicro = fee
}
}
return
}
item.ProviderCode = externalProviderCode
item.UserPaidCurrencyCode = externalCurrencyCode
if item.ProviderAmountMinor > 0 {
item.UserPaidAmountMicro = item.ProviderAmountMinor * 10_000
}
// V5Pay 的回调和查单快照带 fee/tax/accAmountMiFaPay 与 USDT 没有该数据,解析不到就保持 0。
fee, tax, net := externalRechargeSettlementFromPayload(externalPayloadJSON)
item.ProviderFeeMicro = fee
item.ProviderTaxMicro = tax
item.ProviderNetMicro = net
}
// externalRechargeSettlementFromPayload 从 provider_payload 快照解析三方结算字段(实付币种微单位)。
func externalRechargeSettlementFromPayload(payloadJSON string) (feeMicro int64, taxMicro int64, netMicro int64) {
payloadJSON = strings.TrimSpace(payloadJSON)
if payloadJSON == "" || !strings.Contains(payloadJSON, "accAmount") && !strings.Contains(payloadJSON, "\"fee\"") {
return 0, 0, 0
}
var payload struct {
Fee string `json:"fee"`
Tax string `json:"tax"`
AccAmount string `json:"accAmount"`
}
if err := json.Unmarshal([]byte(payloadJSON), &payload); err != nil {
return 0, 0, 0
}
return decimalAmountToMicro(payload.Fee), decimalAmountToMicro(payload.Tax), decimalAmountToMicro(payload.AccAmount)
}
// decimalAmountToMicro 把三方返回的十进制金额字符串(如 "10.50")转成微单位;非法或负数一律按 0 处理。
func decimalAmountToMicro(value string) int64 {
value = strings.TrimSpace(value)
if value == "" {
return 0
}
parsed, err := strconv.ParseFloat(value, 64)
if err != nil || math.IsNaN(parsed) || math.IsInf(parsed, 0) || parsed <= 0 {
return 0
}
return int64(math.Round(parsed * 1_000_000))
}
// SummarizeRechargeBills 按与列表一致的筛选口径聚合充值账单,拆分 Google 与三方两个渠道桶。
func (r *Repository) SummarizeRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) (ledger.RechargeBillSummary, error) {
if r == nil || r.db == nil {
return ledger.RechargeBillSummary{}, xerr.New(xerr.Unavailable, "mysql repository is not configured")
}
ctx = contextWithCommandApp(ctx, query.AppCode)
query = normalizeRechargeBillsQuery(query)
query.AppCode = appcode.FromContext(ctx)
where, args := rechargeBillsWhereSQL(query)
rows, err := r.db.QueryContext(ctx, `
SELECT wt.biz_type, COUNT(*), COALESCE(SUM(rr.coin_amount), 0), COALESCE(SUM(rr.usd_minor_amount), 0)
FROM wallet_recharge_records rr
JOIN wallet_transactions wt ON wt.app_code = rr.app_code AND wt.transaction_id = rr.transaction_id
`+where+`
GROUP BY wt.biz_type`,
args...,
)
if err != nil {
return ledger.RechargeBillSummary{}, err
}
defer rows.Close()
var summary ledger.RechargeBillSummary
for rows.Next() {
var bizType string
var bucket ledger.RechargeBillSummaryBucket
if err := rows.Scan(&bizType, &bucket.BillCount, &bucket.CoinAmount, &bucket.USDMinorAmount); err != nil {
return ledger.RechargeBillSummary{}, err
}
summary.Total.BillCount += bucket.BillCount
summary.Total.CoinAmount += bucket.CoinAmount
summary.Total.USDMinorAmount += bucket.USDMinorAmount
if bizType == bizTypeGooglePlayRecharge {
summary.GooglePlay.BillCount += bucket.BillCount
summary.GooglePlay.CoinAmount += bucket.CoinAmount
summary.GooglePlay.USDMinorAmount += bucket.USDMinorAmount
} else {
summary.ThirdParty.BillCount += bucket.BillCount
summary.ThirdParty.CoinAmount += bucket.CoinAmount
summary.ThirdParty.USDMinorAmount += bucket.USDMinorAmount
}
}
if err := rows.Err(); err != nil {
return ledger.RechargeBillSummary{}, err
}
return summary, nil
}
func (r *Repository) countRechargeBillRows(ctx context.Context, where string, args ...any) (int64, error) { func (r *Repository) countRechargeBillRows(ctx context.Context, where string, args ...any) (int64, error) {
var total int64 var total int64
err := r.db.QueryRowContext(ctx, ` err := r.db.QueryRowContext(ctx, `

View File

@ -57,6 +57,10 @@ func Open(ctx context.Context, dsn string) (*Repository, error) {
_ = db.Close() _ = db.Close()
return nil, err return nil, err
} }
if err := ensureGooglePaymentPaidDetailsSchema(ctx, db); err != nil {
_ = db.Close()
return nil, err
}
return &Repository{db: db}, nil return &Repository{db: db}, nil
} }

View File

@ -158,6 +158,29 @@ func ensureExternalRechargeSchema(ctx context.Context, db *sql.DB) error {
return seedThirdPartyPaymentDefaults(ctx, db) return seedThirdPartyPaymentDefaults(ctx, db)
} }
// ensureGooglePaymentPaidDetailsSchema 给 payment_orders 补 Google Orders API 实付明细列;
// 财务列表按 wallet_transaction_id 反查 Google 支付订单,需要独立索引避免回表扫描。
func ensureGooglePaymentPaidDetailsSchema(ctx context.Context, db *sql.DB) error {
alters := []string{
`ALTER TABLE payment_orders ADD COLUMN paid_currency_code VARCHAR(8) NOT NULL DEFAULT '' COMMENT '用户实付币种,来自 Google Orders API' AFTER amount_micro`,
`ALTER TABLE payment_orders ADD COLUMN paid_amount_micro BIGINT NOT NULL DEFAULT 0 COMMENT '用户实付总额微单位(实付币种)' AFTER paid_currency_code`,
`ALTER TABLE payment_orders ADD COLUMN paid_tax_micro BIGINT NOT NULL DEFAULT 0 COMMENT '订单税额微单位(实付币种)' AFTER paid_amount_micro`,
`ALTER TABLE payment_orders ADD COLUMN paid_net_micro BIGINT NOT NULL DEFAULT 0 COMMENT 'Google 扣费后开发者净收入微单位(实付币种)' AFTER paid_tax_micro`,
`ALTER TABLE payment_orders ADD COLUMN paid_synced_at_ms BIGINT NOT NULL DEFAULT 0 COMMENT '实付明细同步时间UTC epoch ms0 表示未同步' AFTER paid_net_micro`,
}
for _, stmt := range alters {
if _, err := db.ExecContext(ctx, stmt); err != nil && !isDuplicateColumnError(err) {
return err
}
}
if _, err := db.ExecContext(ctx, `
ALTER TABLE payment_orders
ADD INDEX idx_payment_orders_wallet_tx (app_code, wallet_transaction_id)`); err != nil && !isDuplicateKeyNameError(err) {
return err
}
return nil
}
func ensureGiftDiamondRatioSchema(ctx context.Context, db *sql.DB) error { func ensureGiftDiamondRatioSchema(ctx context.Context, db *sql.DB) error {
if _, err := db.ExecContext(ctx, ` if _, err := db.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS gift_diamond_ratio_configs ( CREATE TABLE IF NOT EXISTS gift_diamond_ratio_configs (

View File

@ -36,6 +36,60 @@ func (s *Server) ListRechargeBills(ctx context.Context, req *walletv1.ListRechar
return resp, nil return resp, nil
} }
// GetRechargeBillSummary 暴露充值账单聚合查询,筛选口径与列表完全一致。
func (s *Server) GetRechargeBillSummary(ctx context.Context, req *walletv1.GetRechargeBillSummaryRequest) (*walletv1.GetRechargeBillSummaryResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
summary, err := s.svc.GetRechargeBillSummary(ctx, ledger.ListRechargeBillsQuery{
AppCode: req.GetAppCode(),
UserID: req.GetUserId(),
SellerUserID: req.GetSellerUserId(),
RegionID: req.GetRegionId(),
RechargeType: req.GetRechargeType(),
Status: req.GetStatus(),
Keyword: req.GetKeyword(),
StartAtMS: req.GetStartAtMs(),
EndAtMS: req.GetEndAtMs(),
})
if err != nil {
return nil, xerr.ToGRPCError(err)
}
return &walletv1.GetRechargeBillSummaryResponse{
Total: rechargeBillSummaryBucketToProto(summary.Total),
GooglePlay: rechargeBillSummaryBucketToProto(summary.GooglePlay),
ThirdParty: rechargeBillSummaryBucketToProto(summary.ThirdParty),
}, nil
}
// RefreshGooglePaymentPrices 触发 Google Orders API 实付明细同步;单笔失败不阻断整批。
func (s *Server) RefreshGooglePaymentPrices(ctx context.Context, req *walletv1.RefreshGooglePaymentPricesRequest) (*walletv1.RefreshGooglePaymentPricesResponse, error) {
ctx = appcode.WithContext(ctx, req.GetAppCode())
results, err := s.svc.RefreshGooglePaymentPrices(ctx, req.GetAppCode(), req.GetTransactionIds())
if err != nil {
return nil, xerr.ToGRPCError(err)
}
resp := &walletv1.RefreshGooglePaymentPricesResponse{Prices: make([]*walletv1.GooglePaymentPrice, 0, len(results))}
for _, result := range results {
resp.Prices = append(resp.Prices, &walletv1.GooglePaymentPrice{
TransactionId: result.TransactionID,
CurrencyCode: result.CurrencyCode,
PaidAmountMicro: result.PaidAmountMicro,
TaxMicro: result.TaxAmountMicro,
NetMicro: result.NetAmountMicro,
SyncedAtMs: result.SyncedAtMS,
Error: result.Err,
})
}
return resp, nil
}
func rechargeBillSummaryBucketToProto(bucket ledger.RechargeBillSummaryBucket) *walletv1.RechargeBillSummaryBucket {
return &walletv1.RechargeBillSummaryBucket{
BillCount: bucket.BillCount,
CoinAmount: bucket.CoinAmount,
UsdMinorAmount: bucket.USDMinorAmount,
}
}
func rechargeBillToProto(item ledger.RechargeBill) *walletv1.RechargeBill { func rechargeBillToProto(item ledger.RechargeBill) *walletv1.RechargeBill {
return &walletv1.RechargeBill{ return &walletv1.RechargeBill{
AppCode: item.AppCode, AppCode: item.AppCode,
@ -57,5 +111,12 @@ func rechargeBillToProto(item ledger.RechargeBill) *walletv1.RechargeBill {
ExchangeUsdMinorAmount: item.ExchangeUSDMinorAmount, ExchangeUsdMinorAmount: item.ExchangeUSDMinorAmount,
ProviderAmountMinor: item.ProviderAmountMinor, ProviderAmountMinor: item.ProviderAmountMinor,
CreatedAtMs: item.CreatedAtMS, CreatedAtMs: item.CreatedAtMS,
ProviderCode: item.ProviderCode,
UserPaidCurrencyCode: item.UserPaidCurrencyCode,
UserPaidAmountMicro: item.UserPaidAmountMicro,
ProviderFeeMicro: item.ProviderFeeMicro,
ProviderTaxMicro: item.ProviderTaxMicro,
ProviderNetMicro: item.ProviderNetMicro,
PaidSyncedAtMs: item.PaidSyncedAtMS,
} }
} }