财务模块
This commit is contained in:
parent
a1e7514af3
commit
0480304483
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
12
scripts/mysql/055_google_payment_paid_details.sql
Normal file
12
scripts/mysql/055_google_payment_paid_details.sql
Normal 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 ms;0 表示未同步' AFTER paid_net_micro;
|
||||||
|
|
||||||
|
ALTER TABLE payment_orders
|
||||||
|
ADD INDEX idx_payment_orders_wallet_tx (app_code, wallet_transaction_id);
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
196
server/admin/internal/modules/payment/recharge_bill_stats.go
Normal file
196
server/admin/internal/modules/payment/recharge_bill_stats.go
Normal 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
|
||||||
|
}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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 ms;0 表示未同步',
|
||||||
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 (
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 是后台查询所有充值渠道账单的筛选条件。
|
||||||
|
|||||||
@ -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 API,service 只依赖购买校验和消耗能力。
|
// GooglePlayClient 隔离 Google Play Developer API,service 只依赖购买校验、消耗和订单实付查询能力。
|
||||||
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 的已校验下单快照。
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"`
|
||||||
|
|||||||
@ -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/accAmount;MiFaPay 与 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, `
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 ms;0 表示未同步' 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 (
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user