diff --git a/api/proto/wallet/v1/wallet.pb.go b/api/proto/wallet/v1/wallet.pb.go index 50087ac0..d4955e05 100644 --- a/api/proto/wallet/v1/wallet.pb.go +++ b/api/proto/wallet/v1/wallet.pb.go @@ -10676,6 +10676,7 @@ type RedPacketConfig struct { UpdatedByAdminId int64 `protobuf:"varint,8,opt,name=updated_by_admin_id,json=updatedByAdminId,proto3" json:"updated_by_admin_id,omitempty"` CreatedAtMs int64 `protobuf:"varint,9,opt,name=created_at_ms,json=createdAtMs,proto3" json:"created_at_ms,omitempty"` UpdatedAtMs int64 `protobuf:"varint,10,opt,name=updated_at_ms,json=updatedAtMs,proto3" json:"updated_at_ms,omitempty"` + RuleUrl string `protobuf:"bytes,11,opt,name=rule_url,json=ruleUrl,proto3" json:"rule_url,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -10780,6 +10781,13 @@ func (x *RedPacketConfig) GetUpdatedAtMs() int64 { return 0 } +func (x *RedPacketConfig) GetRuleUrl() string { + if x != nil { + return x.RuleUrl + } + return "" +} + type RedPacketClaim struct { state protoimpl.MessageState `protogen:"open.v1"` AppCode string `protobuf:"bytes,1,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` @@ -10925,6 +10933,9 @@ type RedPacket struct { ClaimedByViewer bool `protobuf:"varint,17,opt,name=claimed_by_viewer,json=claimedByViewer,proto3" json:"claimed_by_viewer,omitempty"` ViewerClaimAmount int64 `protobuf:"varint,18,opt,name=viewer_claim_amount,json=viewerClaimAmount,proto3" json:"viewer_claim_amount,omitempty"` Claims []*RedPacketClaim `protobuf:"bytes,19,rep,name=claims,proto3" json:"claims,omitempty"` + RefundAmount int64 `protobuf:"varint,20,opt,name=refund_amount,json=refundAmount,proto3" json:"refund_amount,omitempty"` + RefundStatus string `protobuf:"bytes,21,opt,name=refund_status,json=refundStatus,proto3" json:"refund_status,omitempty"` + RefundedAtMs int64 `protobuf:"varint,22,opt,name=refunded_at_ms,json=refundedAtMs,proto3" json:"refunded_at_ms,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -11092,6 +11103,27 @@ func (x *RedPacket) GetClaims() []*RedPacketClaim { return nil } +func (x *RedPacket) GetRefundAmount() int64 { + if x != nil { + return x.RefundAmount + } + return 0 +} + +func (x *RedPacket) GetRefundStatus() string { + if x != nil { + return x.RefundStatus + } + return "" +} + +func (x *RedPacket) GetRefundedAtMs() int64 { + if x != nil { + return x.RefundedAtMs + } + return 0 +} + type GetRedPacketConfigRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -11207,6 +11239,7 @@ type UpdateRedPacketConfigRequest struct { ExpireSeconds int32 `protobuf:"varint,7,opt,name=expire_seconds,json=expireSeconds,proto3" json:"expire_seconds,omitempty"` DailySendLimit int32 `protobuf:"varint,8,opt,name=daily_send_limit,json=dailySendLimit,proto3" json:"daily_send_limit,omitempty"` OperatorUserId int64 `protobuf:"varint,9,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + RuleUrl string `protobuf:"bytes,10,opt,name=rule_url,json=ruleUrl,proto3" json:"rule_url,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -11304,6 +11337,13 @@ func (x *UpdateRedPacketConfigRequest) GetOperatorUserId() int64 { return 0 } +func (x *UpdateRedPacketConfigRequest) GetRuleUrl() string { + if x != nil { + return x.RuleUrl + } + return "" +} + type UpdateRedPacketConfigResponse struct { state protoimpl.MessageState `protogen:"open.v1"` Config *RedPacketConfig `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"` @@ -11605,6 +11645,7 @@ type ClaimRedPacketResponse struct { Claim *RedPacketClaim `protobuf:"bytes,1,opt,name=claim,proto3" json:"claim,omitempty"` BalanceAfter int64 `protobuf:"varint,2,opt,name=balance_after,json=balanceAfter,proto3" json:"balance_after,omitempty"` ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + Packet *RedPacket `protobuf:"bytes,4,opt,name=packet,proto3" json:"packet,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -11660,6 +11701,13 @@ func (x *ClaimRedPacketResponse) GetServerTimeMs() int64 { return 0 } +func (x *ClaimRedPacketResponse) GetPacket() *RedPacket { + if x != nil { + return x.Packet + } + return nil +} + type ListRedPacketsRequest struct { state protoimpl.MessageState `protogen:"open.v1"` RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` @@ -12100,6 +12148,134 @@ func (x *ExpireRedPacketsResponse) GetServerTimeMs() int64 { return 0 } +type RetryRedPacketRefundRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + RequestId string `protobuf:"bytes,1,opt,name=request_id,json=requestId,proto3" json:"request_id,omitempty"` + AppCode string `protobuf:"bytes,2,opt,name=app_code,json=appCode,proto3" json:"app_code,omitempty"` + PacketId string `protobuf:"bytes,3,opt,name=packet_id,json=packetId,proto3" json:"packet_id,omitempty"` + OperatorUserId int64 `protobuf:"varint,4,opt,name=operator_user_id,json=operatorUserId,proto3" json:"operator_user_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetryRedPacketRefundRequest) Reset() { + *x = RetryRedPacketRefundRequest{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetryRedPacketRefundRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetryRedPacketRefundRequest) ProtoMessage() {} + +func (x *RetryRedPacketRefundRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[137] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetryRedPacketRefundRequest.ProtoReflect.Descriptor instead. +func (*RetryRedPacketRefundRequest) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{137} +} + +func (x *RetryRedPacketRefundRequest) GetRequestId() string { + if x != nil { + return x.RequestId + } + return "" +} + +func (x *RetryRedPacketRefundRequest) GetAppCode() string { + if x != nil { + return x.AppCode + } + return "" +} + +func (x *RetryRedPacketRefundRequest) GetPacketId() string { + if x != nil { + return x.PacketId + } + return "" +} + +func (x *RetryRedPacketRefundRequest) GetOperatorUserId() int64 { + if x != nil { + return x.OperatorUserId + } + return 0 +} + +type RetryRedPacketRefundResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Packet *RedPacket `protobuf:"bytes,1,opt,name=packet,proto3" json:"packet,omitempty"` + RefundedAmount int64 `protobuf:"varint,2,opt,name=refunded_amount,json=refundedAmount,proto3" json:"refunded_amount,omitempty"` + ServerTimeMs int64 `protobuf:"varint,3,opt,name=server_time_ms,json=serverTimeMs,proto3" json:"server_time_ms,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RetryRedPacketRefundResponse) Reset() { + *x = RetryRedPacketRefundResponse{} + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RetryRedPacketRefundResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RetryRedPacketRefundResponse) ProtoMessage() {} + +func (x *RetryRedPacketRefundResponse) ProtoReflect() protoreflect.Message { + mi := &file_proto_wallet_v1_wallet_proto_msgTypes[138] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RetryRedPacketRefundResponse.ProtoReflect.Descriptor instead. +func (*RetryRedPacketRefundResponse) Descriptor() ([]byte, []int) { + return file_proto_wallet_v1_wallet_proto_rawDescGZIP(), []int{138} +} + +func (x *RetryRedPacketRefundResponse) GetPacket() *RedPacket { + if x != nil { + return x.Packet + } + return nil +} + +func (x *RetryRedPacketRefundResponse) GetRefundedAmount() int64 { + if x != nil { + return x.RefundedAmount + } + return 0 +} + +func (x *RetryRedPacketRefundResponse) GetServerTimeMs() int64 { + if x != nil { + return x.ServerTimeMs + } + return 0 +} + var File_proto_wallet_v1_wallet_proto protoreflect.FileDescriptor const file_proto_wallet_v1_wallet_proto_rawDesc = "" + @@ -13214,7 +13390,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x1bApplyGameCoinChangeResponse\x122\n" + "\x15wallet_transaction_id\x18\x01 \x01(\tR\x13walletTransactionId\x12#\n" + "\rbalance_after\x18\x02 \x01(\x03R\fbalanceAfter\x12+\n" + - "\x11idempotent_replay\x18\x03 \x01(\bR\x10idempotentReplay\"\x84\x03\n" + + "\x11idempotent_replay\x18\x03 \x01(\bR\x10idempotentReplay\"\x9f\x03\n" + "\x0fRedPacketConfig\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x18\n" + "\aenabled\x18\x02 \x01(\bR\aenabled\x12\x1f\n" + @@ -13227,7 +13403,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x13updated_by_admin_id\x18\b \x01(\x03R\x10updatedByAdminId\x12\"\n" + "\rcreated_at_ms\x18\t \x01(\x03R\vcreatedAtMs\x12\"\n" + "\rupdated_at_ms\x18\n" + - " \x01(\x03R\vupdatedAtMs\"\xee\x02\n" + + " \x01(\x03R\vupdatedAtMs\x12\x19\n" + + "\brule_url\x18\v \x01(\tR\aruleUrl\"\xee\x02\n" + "\x0eRedPacketClaim\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x19\n" + "\bclaim_id\x18\x02 \x01(\tR\aclaimId\x12\x1d\n" + @@ -13241,7 +13418,7 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x0efailure_reason\x18\t \x01(\tR\rfailureReason\x12\"\n" + "\rcreated_at_ms\x18\n" + " \x01(\x03R\vcreatedAtMs\x12\"\n" + - "\rupdated_at_ms\x18\v \x01(\x03R\vupdatedAtMs\"\xb0\x05\n" + + "\rupdated_at_ms\x18\v \x01(\x03R\vupdatedAtMs\"\xa0\x06\n" + "\tRedPacket\x12\x19\n" + "\bapp_code\x18\x01 \x01(\tR\aappCode\x12\x1b\n" + "\tpacket_id\x18\x02 \x01(\tR\bpacketId\x12\x1d\n" + @@ -13265,14 +13442,17 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\rupdated_at_ms\x18\x10 \x01(\x03R\vupdatedAtMs\x12*\n" + "\x11claimed_by_viewer\x18\x11 \x01(\bR\x0fclaimedByViewer\x12.\n" + "\x13viewer_claim_amount\x18\x12 \x01(\x03R\x11viewerClaimAmount\x127\n" + - "\x06claims\x18\x13 \x03(\v2\x1f.hyapp.wallet.v1.RedPacketClaimR\x06claims\"U\n" + + "\x06claims\x18\x13 \x03(\v2\x1f.hyapp.wallet.v1.RedPacketClaimR\x06claims\x12#\n" + + "\rrefund_amount\x18\x14 \x01(\x03R\frefundAmount\x12#\n" + + "\rrefund_status\x18\x15 \x01(\tR\frefundStatus\x12$\n" + + "\x0erefunded_at_ms\x18\x16 \x01(\x03R\frefundedAtMs\"U\n" + "\x19GetRedPacketConfigRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + "\bapp_code\x18\x02 \x01(\tR\aappCode\"|\n" + "\x1aGetRedPacketConfigResponse\x128\n" + "\x06config\x18\x01 \x01(\v2 .hyapp.wallet.v1.RedPacketConfigR\x06config\x12$\n" + - "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xe3\x02\n" + + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xfe\x02\n" + "\x1cUpdateRedPacketConfigRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + @@ -13284,7 +13464,9 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x14delayed_open_seconds\x18\x06 \x01(\x05R\x12delayedOpenSeconds\x12%\n" + "\x0eexpire_seconds\x18\a \x01(\x05R\rexpireSeconds\x12(\n" + "\x10daily_send_limit\x18\b \x01(\x05R\x0edailySendLimit\x12(\n" + - "\x10operator_user_id\x18\t \x01(\x03R\x0eoperatorUserId\"\x7f\n" + + "\x10operator_user_id\x18\t \x01(\x03R\x0eoperatorUserId\x12\x19\n" + + "\brule_url\x18\n" + + " \x01(\tR\aruleUrl\"\x7f\n" + "\x1dUpdateRedPacketConfigResponse\x128\n" + "\x06config\x18\x01 \x01(\v2 .hyapp.wallet.v1.RedPacketConfigR\x06config\x12$\n" + "\x0eserver_time_ms\x18\x02 \x01(\x03R\fserverTimeMs\"\xb4\x02\n" + @@ -13312,11 +13494,12 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\n" + "command_id\x18\x03 \x01(\tR\tcommandId\x12\x1b\n" + "\tpacket_id\x18\x04 \x01(\tR\bpacketId\x12\x17\n" + - "\auser_id\x18\x05 \x01(\x03R\x06userId\"\x9a\x01\n" + + "\auser_id\x18\x05 \x01(\x03R\x06userId\"\xce\x01\n" + "\x16ClaimRedPacketResponse\x125\n" + "\x05claim\x18\x01 \x01(\v2\x1f.hyapp.wallet.v1.RedPacketClaimR\x05claim\x12#\n" + "\rbalance_after\x18\x02 \x01(\x03R\fbalanceAfter\x12$\n" + - "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\xf9\x02\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\x122\n" + + "\x06packet\x18\x04 \x01(\v2\x1a.hyapp.wallet.v1.RedPacketR\x06packet\"\xf9\x02\n" + "\x15ListRedPacketsRequest\x12\x1d\n" + "\n" + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + @@ -13356,7 +13539,17 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x18ExpireRedPacketsResponse\x12#\n" + "\rexpired_count\x18\x01 \x01(\x05R\fexpiredCount\x12'\n" + "\x0frefunded_amount\x18\x02 \x01(\x03R\x0erefundedAmount\x12$\n" + - "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs2\xf01\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs\"\x9e\x01\n" + + "\x1bRetryRedPacketRefundRequest\x12\x1d\n" + + "\n" + + "request_id\x18\x01 \x01(\tR\trequestId\x12\x19\n" + + "\bapp_code\x18\x02 \x01(\tR\aappCode\x12\x1b\n" + + "\tpacket_id\x18\x03 \x01(\tR\bpacketId\x12(\n" + + "\x10operator_user_id\x18\x04 \x01(\x03R\x0eoperatorUserId\"\xa1\x01\n" + + "\x1cRetryRedPacketRefundResponse\x122\n" + + "\x06packet\x18\x01 \x01(\v2\x1a.hyapp.wallet.v1.RedPacketR\x06packet\x12'\n" + + "\x0frefunded_amount\x18\x02 \x01(\x03R\x0erefundedAmount\x12$\n" + + "\x0eserver_time_ms\x18\x03 \x01(\x03R\fserverTimeMs2\xe52\n" + "\rWalletService\x12R\n" + "\tDebitGift\x12!.hyapp.wallet.v1.DebitGiftRequest\x1a\".hyapp.wallet.v1.DebitGiftResponse\x12X\n" + "\vGetBalances\x12#.hyapp.wallet.v1.GetBalancesRequest\x1a$.hyapp.wallet.v1.GetBalancesResponse\x12g\n" + @@ -13416,7 +13609,8 @@ const file_proto_wallet_v1_wallet_proto_rawDesc = "" + "\x0eClaimRedPacket\x12&.hyapp.wallet.v1.ClaimRedPacketRequest\x1a'.hyapp.wallet.v1.ClaimRedPacketResponse\x12a\n" + "\x0eListRedPackets\x12&.hyapp.wallet.v1.ListRedPacketsRequest\x1a'.hyapp.wallet.v1.ListRedPacketsResponse\x12[\n" + "\fGetRedPacket\x12$.hyapp.wallet.v1.GetRedPacketRequest\x1a%.hyapp.wallet.v1.GetRedPacketResponse\x12g\n" + - "\x10ExpireRedPackets\x12(.hyapp.wallet.v1.ExpireRedPacketsRequest\x1a).hyapp.wallet.v1.ExpireRedPacketsResponseB*Z(hyapp.local/api/proto/wallet/v1;walletv1b\x06proto3" + "\x10ExpireRedPackets\x12(.hyapp.wallet.v1.ExpireRedPacketsRequest\x1a).hyapp.wallet.v1.ExpireRedPacketsResponse\x12s\n" + + "\x14RetryRedPacketRefund\x12,.hyapp.wallet.v1.RetryRedPacketRefundRequest\x1a-.hyapp.wallet.v1.RetryRedPacketRefundResponseB*Z(hyapp.local/api/proto/wallet/v1;walletv1b\x06proto3" var ( file_proto_wallet_v1_wallet_proto_rawDescOnce sync.Once @@ -13430,7 +13624,7 @@ func file_proto_wallet_v1_wallet_proto_rawDescGZIP() []byte { return file_proto_wallet_v1_wallet_proto_rawDescData } -var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 137) +var file_proto_wallet_v1_wallet_proto_msgTypes = make([]protoimpl.MessageInfo, 139) var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*DebitGiftRequest)(nil), // 0: hyapp.wallet.v1.DebitGiftRequest (*DebitGiftResponse)(nil), // 1: hyapp.wallet.v1.DebitGiftResponse @@ -13569,6 +13763,8 @@ var file_proto_wallet_v1_wallet_proto_goTypes = []any{ (*GetRedPacketResponse)(nil), // 134: hyapp.wallet.v1.GetRedPacketResponse (*ExpireRedPacketsRequest)(nil), // 135: hyapp.wallet.v1.ExpireRedPacketsRequest (*ExpireRedPacketsResponse)(nil), // 136: hyapp.wallet.v1.ExpireRedPacketsResponse + (*RetryRedPacketRefundRequest)(nil), // 137: hyapp.wallet.v1.RetryRedPacketRefundRequest + (*RetryRedPacketRefundResponse)(nil), // 138: hyapp.wallet.v1.RetryRedPacketRefundResponse } var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 2, // 0: hyapp.wallet.v1.GetBalancesResponse.balances:type_name -> hyapp.wallet.v1.AssetBalance @@ -13634,131 +13830,135 @@ var file_proto_wallet_v1_wallet_proto_depIdxs = []int32{ 120, // 60: hyapp.wallet.v1.UpdateRedPacketConfigResponse.config:type_name -> hyapp.wallet.v1.RedPacketConfig 122, // 61: hyapp.wallet.v1.CreateRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket 121, // 62: hyapp.wallet.v1.ClaimRedPacketResponse.claim:type_name -> hyapp.wallet.v1.RedPacketClaim - 122, // 63: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket - 122, // 64: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket - 0, // 65: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest - 3, // 66: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest - 5, // 67: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest - 7, // 68: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest - 9, // 69: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest - 21, // 70: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest - 23, // 71: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest - 25, // 72: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest - 26, // 73: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest - 27, // 74: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest - 29, // 75: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest - 31, // 76: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest - 33, // 77: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest - 34, // 78: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest - 35, // 79: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest - 37, // 80: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest - 39, // 81: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest - 43, // 82: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest - 44, // 83: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest - 45, // 84: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest - 41, // 85: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest - 47, // 86: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest - 48, // 87: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest - 50, // 88: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest - 52, // 89: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest - 54, // 90: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest - 57, // 91: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest - 59, // 92: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest - 61, // 93: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest - 63, // 94: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest - 66, // 95: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest - 69, // 96: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest - 72, // 97: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest - 75, // 98: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest - 78, // 99: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest - 80, // 100: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest - 82, // 101: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest - 84, // 102: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest - 85, // 103: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest - 86, // 104: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest - 90, // 105: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest - 93, // 106: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest - 96, // 107: hyapp.wallet.v1.WalletService.ApplyWithdrawal:input_type -> hyapp.wallet.v1.ApplyWithdrawalRequest - 101, // 108: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest - 103, // 109: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest - 105, // 110: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest - 107, // 111: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest - 110, // 112: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest - 112, // 113: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest - 114, // 114: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest - 116, // 115: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest - 118, // 116: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest - 123, // 117: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest - 125, // 118: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest - 127, // 119: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest - 129, // 120: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest - 131, // 121: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest - 133, // 122: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest - 135, // 123: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest - 1, // 124: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse - 4, // 125: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse - 6, // 126: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse - 8, // 127: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse - 10, // 128: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse - 22, // 129: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse - 24, // 130: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse - 28, // 131: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 28, // 132: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse - 28, // 133: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse - 30, // 134: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse - 32, // 135: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse - 36, // 136: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 36, // 137: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 36, // 138: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse - 38, // 139: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse - 40, // 140: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse - 46, // 141: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 46, // 142: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse - 46, // 143: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse - 42, // 144: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse - 49, // 145: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 49, // 146: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse - 51, // 147: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse - 53, // 148: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse - 55, // 149: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse - 58, // 150: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse - 60, // 151: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse - 62, // 152: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse - 64, // 153: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse - 67, // 154: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse - 70, // 155: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse - 73, // 156: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse - 76, // 157: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse - 79, // 158: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse - 81, // 159: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse - 83, // 160: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse - 87, // 161: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 87, // 162: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse - 88, // 163: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse - 91, // 164: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse - 94, // 165: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse - 97, // 166: hyapp.wallet.v1.WalletService.ApplyWithdrawal:output_type -> hyapp.wallet.v1.ApplyWithdrawalResponse - 102, // 167: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse - 104, // 168: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse - 106, // 169: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse - 108, // 170: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse - 111, // 171: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse - 113, // 172: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse - 115, // 173: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse - 117, // 174: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse - 119, // 175: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse - 124, // 176: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse - 126, // 177: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse - 128, // 178: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse - 130, // 179: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse - 132, // 180: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse - 134, // 181: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse - 136, // 182: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse - 124, // [124:183] is the sub-list for method output_type - 65, // [65:124] is the sub-list for method input_type - 65, // [65:65] is the sub-list for extension type_name - 65, // [65:65] is the sub-list for extension extendee - 0, // [0:65] is the sub-list for field type_name + 122, // 63: hyapp.wallet.v1.ClaimRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 122, // 64: hyapp.wallet.v1.ListRedPacketsResponse.packets:type_name -> hyapp.wallet.v1.RedPacket + 122, // 65: hyapp.wallet.v1.GetRedPacketResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 122, // 66: hyapp.wallet.v1.RetryRedPacketRefundResponse.packet:type_name -> hyapp.wallet.v1.RedPacket + 0, // 67: hyapp.wallet.v1.WalletService.DebitGift:input_type -> hyapp.wallet.v1.DebitGiftRequest + 3, // 68: hyapp.wallet.v1.WalletService.GetBalances:input_type -> hyapp.wallet.v1.GetBalancesRequest + 5, // 69: hyapp.wallet.v1.WalletService.AdminCreditAsset:input_type -> hyapp.wallet.v1.AdminCreditAssetRequest + 7, // 70: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:input_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockRequest + 9, // 71: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:input_type -> hyapp.wallet.v1.TransferCoinFromSellerRequest + 21, // 72: hyapp.wallet.v1.WalletService.ListResources:input_type -> hyapp.wallet.v1.ListResourcesRequest + 23, // 73: hyapp.wallet.v1.WalletService.GetResource:input_type -> hyapp.wallet.v1.GetResourceRequest + 25, // 74: hyapp.wallet.v1.WalletService.CreateResource:input_type -> hyapp.wallet.v1.CreateResourceRequest + 26, // 75: hyapp.wallet.v1.WalletService.UpdateResource:input_type -> hyapp.wallet.v1.UpdateResourceRequest + 27, // 76: hyapp.wallet.v1.WalletService.SetResourceStatus:input_type -> hyapp.wallet.v1.SetResourceStatusRequest + 29, // 77: hyapp.wallet.v1.WalletService.ListResourceGroups:input_type -> hyapp.wallet.v1.ListResourceGroupsRequest + 31, // 78: hyapp.wallet.v1.WalletService.GetResourceGroup:input_type -> hyapp.wallet.v1.GetResourceGroupRequest + 33, // 79: hyapp.wallet.v1.WalletService.CreateResourceGroup:input_type -> hyapp.wallet.v1.CreateResourceGroupRequest + 34, // 80: hyapp.wallet.v1.WalletService.UpdateResourceGroup:input_type -> hyapp.wallet.v1.UpdateResourceGroupRequest + 35, // 81: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:input_type -> hyapp.wallet.v1.SetResourceGroupStatusRequest + 37, // 82: hyapp.wallet.v1.WalletService.ListGiftConfigs:input_type -> hyapp.wallet.v1.ListGiftConfigsRequest + 39, // 83: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:input_type -> hyapp.wallet.v1.ListGiftTypeConfigsRequest + 43, // 84: hyapp.wallet.v1.WalletService.CreateGiftConfig:input_type -> hyapp.wallet.v1.CreateGiftConfigRequest + 44, // 85: hyapp.wallet.v1.WalletService.UpdateGiftConfig:input_type -> hyapp.wallet.v1.UpdateGiftConfigRequest + 45, // 86: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:input_type -> hyapp.wallet.v1.SetGiftConfigStatusRequest + 41, // 87: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:input_type -> hyapp.wallet.v1.UpsertGiftTypeConfigRequest + 47, // 88: hyapp.wallet.v1.WalletService.GrantResource:input_type -> hyapp.wallet.v1.GrantResourceRequest + 48, // 89: hyapp.wallet.v1.WalletService.GrantResourceGroup:input_type -> hyapp.wallet.v1.GrantResourceGroupRequest + 50, // 90: hyapp.wallet.v1.WalletService.ListUserResources:input_type -> hyapp.wallet.v1.ListUserResourcesRequest + 52, // 91: hyapp.wallet.v1.WalletService.EquipUserResource:input_type -> hyapp.wallet.v1.EquipUserResourceRequest + 54, // 92: hyapp.wallet.v1.WalletService.ListResourceGrants:input_type -> hyapp.wallet.v1.ListResourceGrantsRequest + 57, // 93: hyapp.wallet.v1.WalletService.ListResourceShopItems:input_type -> hyapp.wallet.v1.ListResourceShopItemsRequest + 59, // 94: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:input_type -> hyapp.wallet.v1.UpsertResourceShopItemsRequest + 61, // 95: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:input_type -> hyapp.wallet.v1.SetResourceShopItemStatusRequest + 63, // 96: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:input_type -> hyapp.wallet.v1.PurchaseResourceShopItemRequest + 66, // 97: hyapp.wallet.v1.WalletService.ListRechargeBills:input_type -> hyapp.wallet.v1.ListRechargeBillsRequest + 69, // 98: hyapp.wallet.v1.WalletService.GetWalletOverview:input_type -> hyapp.wallet.v1.GetWalletOverviewRequest + 72, // 99: hyapp.wallet.v1.WalletService.GetWalletValueSummary:input_type -> hyapp.wallet.v1.GetWalletValueSummaryRequest + 75, // 100: hyapp.wallet.v1.WalletService.GetUserGiftWall:input_type -> hyapp.wallet.v1.GetUserGiftWallRequest + 78, // 101: hyapp.wallet.v1.WalletService.ListRechargeProducts:input_type -> hyapp.wallet.v1.ListRechargeProductsRequest + 80, // 102: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:input_type -> hyapp.wallet.v1.ConfirmGooglePaymentRequest + 82, // 103: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:input_type -> hyapp.wallet.v1.ListAdminRechargeProductsRequest + 84, // 104: hyapp.wallet.v1.WalletService.CreateRechargeProduct:input_type -> hyapp.wallet.v1.CreateRechargeProductRequest + 85, // 105: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:input_type -> hyapp.wallet.v1.UpdateRechargeProductRequest + 86, // 106: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:input_type -> hyapp.wallet.v1.DeleteRechargeProductRequest + 90, // 107: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:input_type -> hyapp.wallet.v1.GetDiamondExchangeConfigRequest + 93, // 108: hyapp.wallet.v1.WalletService.ListWalletTransactions:input_type -> hyapp.wallet.v1.ListWalletTransactionsRequest + 96, // 109: hyapp.wallet.v1.WalletService.ApplyWithdrawal:input_type -> hyapp.wallet.v1.ApplyWithdrawalRequest + 101, // 110: hyapp.wallet.v1.WalletService.ListVipPackages:input_type -> hyapp.wallet.v1.ListVipPackagesRequest + 103, // 111: hyapp.wallet.v1.WalletService.GetMyVip:input_type -> hyapp.wallet.v1.GetMyVipRequest + 105, // 112: hyapp.wallet.v1.WalletService.PurchaseVip:input_type -> hyapp.wallet.v1.PurchaseVipRequest + 107, // 113: hyapp.wallet.v1.WalletService.GrantVip:input_type -> hyapp.wallet.v1.GrantVipRequest + 110, // 114: hyapp.wallet.v1.WalletService.ListAdminVipLevels:input_type -> hyapp.wallet.v1.ListAdminVipLevelsRequest + 112, // 115: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:input_type -> hyapp.wallet.v1.UpdateAdminVipLevelsRequest + 114, // 116: hyapp.wallet.v1.WalletService.CreditTaskReward:input_type -> hyapp.wallet.v1.CreditTaskRewardRequest + 116, // 117: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:input_type -> hyapp.wallet.v1.CreditLuckyGiftRewardRequest + 118, // 118: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:input_type -> hyapp.wallet.v1.ApplyGameCoinChangeRequest + 123, // 119: hyapp.wallet.v1.WalletService.GetRedPacketConfig:input_type -> hyapp.wallet.v1.GetRedPacketConfigRequest + 125, // 120: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:input_type -> hyapp.wallet.v1.UpdateRedPacketConfigRequest + 127, // 121: hyapp.wallet.v1.WalletService.CreateRedPacket:input_type -> hyapp.wallet.v1.CreateRedPacketRequest + 129, // 122: hyapp.wallet.v1.WalletService.ClaimRedPacket:input_type -> hyapp.wallet.v1.ClaimRedPacketRequest + 131, // 123: hyapp.wallet.v1.WalletService.ListRedPackets:input_type -> hyapp.wallet.v1.ListRedPacketsRequest + 133, // 124: hyapp.wallet.v1.WalletService.GetRedPacket:input_type -> hyapp.wallet.v1.GetRedPacketRequest + 135, // 125: hyapp.wallet.v1.WalletService.ExpireRedPackets:input_type -> hyapp.wallet.v1.ExpireRedPacketsRequest + 137, // 126: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:input_type -> hyapp.wallet.v1.RetryRedPacketRefundRequest + 1, // 127: hyapp.wallet.v1.WalletService.DebitGift:output_type -> hyapp.wallet.v1.DebitGiftResponse + 4, // 128: hyapp.wallet.v1.WalletService.GetBalances:output_type -> hyapp.wallet.v1.GetBalancesResponse + 6, // 129: hyapp.wallet.v1.WalletService.AdminCreditAsset:output_type -> hyapp.wallet.v1.AdminCreditAssetResponse + 8, // 130: hyapp.wallet.v1.WalletService.AdminCreditCoinSellerStock:output_type -> hyapp.wallet.v1.AdminCreditCoinSellerStockResponse + 10, // 131: hyapp.wallet.v1.WalletService.TransferCoinFromSeller:output_type -> hyapp.wallet.v1.TransferCoinFromSellerResponse + 22, // 132: hyapp.wallet.v1.WalletService.ListResources:output_type -> hyapp.wallet.v1.ListResourcesResponse + 24, // 133: hyapp.wallet.v1.WalletService.GetResource:output_type -> hyapp.wallet.v1.GetResourceResponse + 28, // 134: hyapp.wallet.v1.WalletService.CreateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 28, // 135: hyapp.wallet.v1.WalletService.UpdateResource:output_type -> hyapp.wallet.v1.ResourceResponse + 28, // 136: hyapp.wallet.v1.WalletService.SetResourceStatus:output_type -> hyapp.wallet.v1.ResourceResponse + 30, // 137: hyapp.wallet.v1.WalletService.ListResourceGroups:output_type -> hyapp.wallet.v1.ListResourceGroupsResponse + 32, // 138: hyapp.wallet.v1.WalletService.GetResourceGroup:output_type -> hyapp.wallet.v1.GetResourceGroupResponse + 36, // 139: hyapp.wallet.v1.WalletService.CreateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 36, // 140: hyapp.wallet.v1.WalletService.UpdateResourceGroup:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 36, // 141: hyapp.wallet.v1.WalletService.SetResourceGroupStatus:output_type -> hyapp.wallet.v1.ResourceGroupResponse + 38, // 142: hyapp.wallet.v1.WalletService.ListGiftConfigs:output_type -> hyapp.wallet.v1.ListGiftConfigsResponse + 40, // 143: hyapp.wallet.v1.WalletService.ListGiftTypeConfigs:output_type -> hyapp.wallet.v1.ListGiftTypeConfigsResponse + 46, // 144: hyapp.wallet.v1.WalletService.CreateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 46, // 145: hyapp.wallet.v1.WalletService.UpdateGiftConfig:output_type -> hyapp.wallet.v1.GiftConfigResponse + 46, // 146: hyapp.wallet.v1.WalletService.SetGiftConfigStatus:output_type -> hyapp.wallet.v1.GiftConfigResponse + 42, // 147: hyapp.wallet.v1.WalletService.UpsertGiftTypeConfig:output_type -> hyapp.wallet.v1.GiftTypeConfigResponse + 49, // 148: hyapp.wallet.v1.WalletService.GrantResource:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 49, // 149: hyapp.wallet.v1.WalletService.GrantResourceGroup:output_type -> hyapp.wallet.v1.ResourceGrantResponse + 51, // 150: hyapp.wallet.v1.WalletService.ListUserResources:output_type -> hyapp.wallet.v1.ListUserResourcesResponse + 53, // 151: hyapp.wallet.v1.WalletService.EquipUserResource:output_type -> hyapp.wallet.v1.EquipUserResourceResponse + 55, // 152: hyapp.wallet.v1.WalletService.ListResourceGrants:output_type -> hyapp.wallet.v1.ListResourceGrantsResponse + 58, // 153: hyapp.wallet.v1.WalletService.ListResourceShopItems:output_type -> hyapp.wallet.v1.ListResourceShopItemsResponse + 60, // 154: hyapp.wallet.v1.WalletService.UpsertResourceShopItems:output_type -> hyapp.wallet.v1.UpsertResourceShopItemsResponse + 62, // 155: hyapp.wallet.v1.WalletService.SetResourceShopItemStatus:output_type -> hyapp.wallet.v1.ResourceShopItemResponse + 64, // 156: hyapp.wallet.v1.WalletService.PurchaseResourceShopItem:output_type -> hyapp.wallet.v1.PurchaseResourceShopItemResponse + 67, // 157: hyapp.wallet.v1.WalletService.ListRechargeBills:output_type -> hyapp.wallet.v1.ListRechargeBillsResponse + 70, // 158: hyapp.wallet.v1.WalletService.GetWalletOverview:output_type -> hyapp.wallet.v1.GetWalletOverviewResponse + 73, // 159: hyapp.wallet.v1.WalletService.GetWalletValueSummary:output_type -> hyapp.wallet.v1.GetWalletValueSummaryResponse + 76, // 160: hyapp.wallet.v1.WalletService.GetUserGiftWall:output_type -> hyapp.wallet.v1.GetUserGiftWallResponse + 79, // 161: hyapp.wallet.v1.WalletService.ListRechargeProducts:output_type -> hyapp.wallet.v1.ListRechargeProductsResponse + 81, // 162: hyapp.wallet.v1.WalletService.ConfirmGooglePayment:output_type -> hyapp.wallet.v1.ConfirmGooglePaymentResponse + 83, // 163: hyapp.wallet.v1.WalletService.ListAdminRechargeProducts:output_type -> hyapp.wallet.v1.ListAdminRechargeProductsResponse + 87, // 164: hyapp.wallet.v1.WalletService.CreateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 87, // 165: hyapp.wallet.v1.WalletService.UpdateRechargeProduct:output_type -> hyapp.wallet.v1.RechargeProductResponse + 88, // 166: hyapp.wallet.v1.WalletService.DeleteRechargeProduct:output_type -> hyapp.wallet.v1.DeleteRechargeProductResponse + 91, // 167: hyapp.wallet.v1.WalletService.GetDiamondExchangeConfig:output_type -> hyapp.wallet.v1.GetDiamondExchangeConfigResponse + 94, // 168: hyapp.wallet.v1.WalletService.ListWalletTransactions:output_type -> hyapp.wallet.v1.ListWalletTransactionsResponse + 97, // 169: hyapp.wallet.v1.WalletService.ApplyWithdrawal:output_type -> hyapp.wallet.v1.ApplyWithdrawalResponse + 102, // 170: hyapp.wallet.v1.WalletService.ListVipPackages:output_type -> hyapp.wallet.v1.ListVipPackagesResponse + 104, // 171: hyapp.wallet.v1.WalletService.GetMyVip:output_type -> hyapp.wallet.v1.GetMyVipResponse + 106, // 172: hyapp.wallet.v1.WalletService.PurchaseVip:output_type -> hyapp.wallet.v1.PurchaseVipResponse + 108, // 173: hyapp.wallet.v1.WalletService.GrantVip:output_type -> hyapp.wallet.v1.GrantVipResponse + 111, // 174: hyapp.wallet.v1.WalletService.ListAdminVipLevels:output_type -> hyapp.wallet.v1.ListAdminVipLevelsResponse + 113, // 175: hyapp.wallet.v1.WalletService.UpdateAdminVipLevels:output_type -> hyapp.wallet.v1.UpdateAdminVipLevelsResponse + 115, // 176: hyapp.wallet.v1.WalletService.CreditTaskReward:output_type -> hyapp.wallet.v1.CreditTaskRewardResponse + 117, // 177: hyapp.wallet.v1.WalletService.CreditLuckyGiftReward:output_type -> hyapp.wallet.v1.CreditLuckyGiftRewardResponse + 119, // 178: hyapp.wallet.v1.WalletService.ApplyGameCoinChange:output_type -> hyapp.wallet.v1.ApplyGameCoinChangeResponse + 124, // 179: hyapp.wallet.v1.WalletService.GetRedPacketConfig:output_type -> hyapp.wallet.v1.GetRedPacketConfigResponse + 126, // 180: hyapp.wallet.v1.WalletService.UpdateRedPacketConfig:output_type -> hyapp.wallet.v1.UpdateRedPacketConfigResponse + 128, // 181: hyapp.wallet.v1.WalletService.CreateRedPacket:output_type -> hyapp.wallet.v1.CreateRedPacketResponse + 130, // 182: hyapp.wallet.v1.WalletService.ClaimRedPacket:output_type -> hyapp.wallet.v1.ClaimRedPacketResponse + 132, // 183: hyapp.wallet.v1.WalletService.ListRedPackets:output_type -> hyapp.wallet.v1.ListRedPacketsResponse + 134, // 184: hyapp.wallet.v1.WalletService.GetRedPacket:output_type -> hyapp.wallet.v1.GetRedPacketResponse + 136, // 185: hyapp.wallet.v1.WalletService.ExpireRedPackets:output_type -> hyapp.wallet.v1.ExpireRedPacketsResponse + 138, // 186: hyapp.wallet.v1.WalletService.RetryRedPacketRefund:output_type -> hyapp.wallet.v1.RetryRedPacketRefundResponse + 127, // [127:187] is the sub-list for method output_type + 67, // [67:127] is the sub-list for method input_type + 67, // [67:67] is the sub-list for extension type_name + 67, // [67:67] is the sub-list for extension extendee + 0, // [0:67] is the sub-list for field type_name } func init() { file_proto_wallet_v1_wallet_proto_init() } @@ -13774,7 +13974,7 @@ func file_proto_wallet_v1_wallet_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_wallet_v1_wallet_proto_rawDesc), len(file_proto_wallet_v1_wallet_proto_rawDesc)), NumEnums: 0, - NumMessages: 137, + NumMessages: 139, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/wallet/v1/wallet.proto b/api/proto/wallet/v1/wallet.proto index 2adcb187..50aab175 100644 --- a/api/proto/wallet/v1/wallet.proto +++ b/api/proto/wallet/v1/wallet.proto @@ -1189,6 +1189,7 @@ message RedPacketConfig { int64 updated_by_admin_id = 8; int64 created_at_ms = 9; int64 updated_at_ms = 10; + string rule_url = 11; } message RedPacketClaim { @@ -1225,6 +1226,9 @@ message RedPacket { bool claimed_by_viewer = 17; int64 viewer_claim_amount = 18; repeated RedPacketClaim claims = 19; + int64 refund_amount = 20; + string refund_status = 21; + int64 refunded_at_ms = 22; } message GetRedPacketConfigRequest { @@ -1247,6 +1251,7 @@ message UpdateRedPacketConfigRequest { int32 expire_seconds = 7; int32 daily_send_limit = 8; int64 operator_user_id = 9; + string rule_url = 10; } message UpdateRedPacketConfigResponse { @@ -1284,6 +1289,7 @@ message ClaimRedPacketResponse { RedPacketClaim claim = 1; int64 balance_after = 2; int64 server_time_ms = 3; + RedPacket packet = 4; } message ListRedPacketsRequest { @@ -1332,6 +1338,19 @@ message ExpireRedPacketsResponse { int64 server_time_ms = 3; } +message RetryRedPacketRefundRequest { + string request_id = 1; + string app_code = 2; + string packet_id = 3; + int64 operator_user_id = 4; +} + +message RetryRedPacketRefundResponse { + RedPacket packet = 1; + int64 refunded_amount = 2; + int64 server_time_ms = 3; +} + // WalletService 是内部账务 gRPC 边界,外部 HTTP 入口仍由 gateway-service 承载。 service WalletService { rpc DebitGift(DebitGiftRequest) returns (DebitGiftResponse); @@ -1393,4 +1412,5 @@ service WalletService { rpc ListRedPackets(ListRedPacketsRequest) returns (ListRedPacketsResponse); rpc GetRedPacket(GetRedPacketRequest) returns (GetRedPacketResponse); rpc ExpireRedPackets(ExpireRedPacketsRequest) returns (ExpireRedPacketsResponse); + rpc RetryRedPacketRefund(RetryRedPacketRefundRequest) returns (RetryRedPacketRefundResponse); } diff --git a/api/proto/wallet/v1/wallet_grpc.pb.go b/api/proto/wallet/v1/wallet_grpc.pb.go index c7ba861a..c3b30f7e 100644 --- a/api/proto/wallet/v1/wallet_grpc.pb.go +++ b/api/proto/wallet/v1/wallet_grpc.pb.go @@ -78,6 +78,7 @@ const ( WalletService_ListRedPackets_FullMethodName = "/hyapp.wallet.v1.WalletService/ListRedPackets" WalletService_GetRedPacket_FullMethodName = "/hyapp.wallet.v1.WalletService/GetRedPacket" WalletService_ExpireRedPackets_FullMethodName = "/hyapp.wallet.v1.WalletService/ExpireRedPackets" + WalletService_RetryRedPacketRefund_FullMethodName = "/hyapp.wallet.v1.WalletService/RetryRedPacketRefund" ) // WalletServiceClient is the client API for WalletService service. @@ -145,6 +146,7 @@ type WalletServiceClient interface { ListRedPackets(ctx context.Context, in *ListRedPacketsRequest, opts ...grpc.CallOption) (*ListRedPacketsResponse, error) GetRedPacket(ctx context.Context, in *GetRedPacketRequest, opts ...grpc.CallOption) (*GetRedPacketResponse, error) ExpireRedPackets(ctx context.Context, in *ExpireRedPacketsRequest, opts ...grpc.CallOption) (*ExpireRedPacketsResponse, error) + RetryRedPacketRefund(ctx context.Context, in *RetryRedPacketRefundRequest, opts ...grpc.CallOption) (*RetryRedPacketRefundResponse, error) } type walletServiceClient struct { @@ -745,6 +747,16 @@ func (c *walletServiceClient) ExpireRedPackets(ctx context.Context, in *ExpireRe return out, nil } +func (c *walletServiceClient) RetryRedPacketRefund(ctx context.Context, in *RetryRedPacketRefundRequest, opts ...grpc.CallOption) (*RetryRedPacketRefundResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(RetryRedPacketRefundResponse) + err := c.cc.Invoke(ctx, WalletService_RetryRedPacketRefund_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // WalletServiceServer is the server API for WalletService service. // All implementations must embed UnimplementedWalletServiceServer // for forward compatibility. @@ -810,6 +822,7 @@ type WalletServiceServer interface { ListRedPackets(context.Context, *ListRedPacketsRequest) (*ListRedPacketsResponse, error) GetRedPacket(context.Context, *GetRedPacketRequest) (*GetRedPacketResponse, error) ExpireRedPackets(context.Context, *ExpireRedPacketsRequest) (*ExpireRedPacketsResponse, error) + RetryRedPacketRefund(context.Context, *RetryRedPacketRefundRequest) (*RetryRedPacketRefundResponse, error) mustEmbedUnimplementedWalletServiceServer() } @@ -997,6 +1010,9 @@ func (UnimplementedWalletServiceServer) GetRedPacket(context.Context, *GetRedPac func (UnimplementedWalletServiceServer) ExpireRedPackets(context.Context, *ExpireRedPacketsRequest) (*ExpireRedPacketsResponse, error) { return nil, status.Error(codes.Unimplemented, "method ExpireRedPackets not implemented") } +func (UnimplementedWalletServiceServer) RetryRedPacketRefund(context.Context, *RetryRedPacketRefundRequest) (*RetryRedPacketRefundResponse, error) { + return nil, status.Error(codes.Unimplemented, "method RetryRedPacketRefund not implemented") +} func (UnimplementedWalletServiceServer) mustEmbedUnimplementedWalletServiceServer() {} func (UnimplementedWalletServiceServer) testEmbeddedByValue() {} @@ -2080,6 +2096,24 @@ func _WalletService_ExpireRedPackets_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _WalletService_RetryRedPacketRefund_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RetryRedPacketRefundRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServiceServer).RetryRedPacketRefund(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: WalletService_RetryRedPacketRefund_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServiceServer).RetryRedPacketRefund(ctx, req.(*RetryRedPacketRefundRequest)) + } + return interceptor(ctx, in, info, handler) +} + // WalletService_ServiceDesc is the grpc.ServiceDesc for WalletService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -2323,6 +2357,10 @@ var WalletService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ExpireRedPackets", Handler: _WalletService_ExpireRedPackets_Handler, }, + { + MethodName: "RetryRedPacketRefund", + Handler: _WalletService_RetryRedPacketRefund_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/wallet/v1/wallet.proto", diff --git a/server/admin/internal/integration/walletclient/client.go b/server/admin/internal/integration/walletclient/client.go index a9184f09..c9ac62ed 100644 --- a/server/admin/internal/integration/walletclient/client.go +++ b/server/admin/internal/integration/walletclient/client.go @@ -46,6 +46,7 @@ type Client interface { UpdateRedPacketConfig(ctx context.Context, req *walletv1.UpdateRedPacketConfigRequest) (*walletv1.UpdateRedPacketConfigResponse, error) ListRedPackets(ctx context.Context, req *walletv1.ListRedPacketsRequest) (*walletv1.ListRedPacketsResponse, error) GetRedPacket(ctx context.Context, req *walletv1.GetRedPacketRequest) (*walletv1.GetRedPacketResponse, error) + RetryRedPacketRefund(ctx context.Context, req *walletv1.RetryRedPacketRefundRequest) (*walletv1.RetryRedPacketRefundResponse, error) } type GRPCClient struct { @@ -199,3 +200,7 @@ func (c *GRPCClient) ListRedPackets(ctx context.Context, req *walletv1.ListRedPa func (c *GRPCClient) GetRedPacket(ctx context.Context, req *walletv1.GetRedPacketRequest) (*walletv1.GetRedPacketResponse, error) { return c.client.GetRedPacket(ctx, req) } + +func (c *GRPCClient) RetryRedPacketRefund(ctx context.Context, req *walletv1.RetryRedPacketRefundRequest) (*walletv1.RetryRedPacketRefundResponse, error) { + return c.client.RetryRedPacketRefund(ctx, req) +} diff --git a/server/admin/internal/modules/redpacket/handler.go b/server/admin/internal/modules/redpacket/handler.go index 8a94c2ac..c67a966c 100644 --- a/server/admin/internal/modules/redpacket/handler.go +++ b/server/admin/internal/modules/redpacket/handler.go @@ -26,10 +26,16 @@ func New(wallet walletclient.Client, audit shared.OperationLogger) *Handler { type configRequest struct { Enabled bool `json:"enabled"` CountTiers []int32 `json:"count_tiers"` + CountOptions []int32 `json:"countOptions"` AmountTiers []int64 `json:"amount_tiers"` + AmountOptions []int64 `json:"amountOptions"` DelayedOpenSeconds int32 `json:"delayed_open_seconds"` + DelaySeconds int32 `json:"delaySeconds"` ExpireSeconds int32 `json:"expire_seconds"` DailySendLimit int32 `json:"daily_send_limit"` + DailySendLimitV2 int32 `json:"dailySendLimit"` + RuleURL string `json:"rule_url"` + RuleURLV2 string `json:"ruleUrl"` } type configDTO struct { @@ -40,6 +46,7 @@ type configDTO struct { DelayedOpenSeconds int32 `json:"delayed_open_seconds"` ExpireSeconds int32 `json:"expire_seconds"` DailySendLimit int32 `json:"daily_send_limit"` + RuleURL string `json:"rule_url"` UpdatedByAdminID int64 `json:"updated_by_admin_id"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` @@ -62,9 +69,17 @@ type packetDTO struct { ExpiresAtMS int64 `json:"expires_at_ms"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` + RefundAmount int64 `json:"refund_amount"` + RefundStatus string `json:"refund_status"` + RefundedAtMS int64 `json:"refunded_at_ms"` Claims []claimDTO `json:"claims,omitempty"` } +type refundRetryRequest struct { + PacketNo string `json:"packetNo"` + PacketID string `json:"packet_id"` +} + type claimDTO struct { AppCode string `json:"app_code"` ClaimID string `json:"claim_id"` @@ -104,15 +119,36 @@ func (h *Handler) UpdateConfig(c *gin.Context) { response.BadRequest(c, "红包配置参数不正确") return } + countTiers := req.CountTiers + if len(countTiers) == 0 { + countTiers = req.CountOptions + } + amountTiers := req.AmountTiers + if len(amountTiers) == 0 { + amountTiers = req.AmountOptions + } + delaySeconds := req.DelayedOpenSeconds + if delaySeconds == 0 { + delaySeconds = req.DelaySeconds + } + dailyLimit := req.DailySendLimit + if dailyLimit == 0 { + dailyLimit = req.DailySendLimitV2 + } + ruleURL := strings.TrimSpace(req.RuleURL) + if ruleURL == "" { + ruleURL = strings.TrimSpace(req.RuleURLV2) + } resp, err := h.wallet.UpdateRedPacketConfig(c.Request.Context(), &walletv1.UpdateRedPacketConfigRequest{ AppCode: appctx.FromContext(c.Request.Context()), Enabled: req.Enabled, - CountTiers: req.CountTiers, - AmountTiers: req.AmountTiers, - DelayedOpenSeconds: req.DelayedOpenSeconds, - ExpireSeconds: req.ExpireSeconds, - DailySendLimit: req.DailySendLimit, + CountTiers: countTiers, + AmountTiers: amountTiers, + DelayedOpenSeconds: delaySeconds, + ExpireSeconds: 86400, + DailySendLimit: dailyLimit, OperatorUserId: int64(middleware.CurrentUserID(c)), + RuleUrl: ruleURL, }) if err != nil { response.BadRequest(c, err.Error()) @@ -136,10 +172,16 @@ func (h *Handler) ListPackets(c *gin.Context) { Status: strings.TrimSpace(options.Status), Page: int32(options.Page), PageSize: int32(options.PageSize), - SenderUserId: parseInt64Query(c, "sender_user_id"), + SenderUserId: firstInt64Query(c, "senderUserId", "sender_user_id"), RegionId: parseInt64Query(c, "region_id"), - StartAtMs: parseInt64Query(c, "start_at_ms"), - EndAtMs: parseInt64Query(c, "end_at_ms"), + StartAtMs: firstInt64Query(c, "startTime", "start_at_ms"), + EndAtMs: firstInt64Query(c, "endTime", "end_at_ms"), + } + if roomID := strings.TrimSpace(c.Query("roomId")); roomID != "" { + req.RoomId = roomID + } + if packetMode := strings.TrimSpace(c.Query("packetMode")); packetMode != "" { + req.PacketType = packetMode } resp, err := h.wallet.ListRedPackets(c.Request.Context(), req) if err != nil { @@ -175,6 +217,41 @@ func (h *Handler) GetPacket(c *gin.Context) { response.OK(c, packetFromProto(resp.GetPacket())) } +func (h *Handler) RetryRefund(c *gin.Context) { + if h.wallet == nil { + response.ServerError(c, "钱包服务未配置") + return + } + var req refundRetryRequest + if err := c.ShouldBindJSON(&req); err != nil { + response.BadRequest(c, "红包退款参数不正确") + return + } + packetID := strings.TrimSpace(req.PacketNo) + if packetID == "" { + packetID = strings.TrimSpace(req.PacketID) + } + if packetID == "" { + response.BadRequest(c, "红包 ID 不正确") + return + } + resp, err := h.wallet.RetryRedPacketRefund(c.Request.Context(), &walletv1.RetryRedPacketRefundRequest{ + AppCode: appctx.FromContext(c.Request.Context()), + PacketId: packetID, + OperatorUserId: int64(middleware.CurrentUserID(c)), + }) + if err != nil { + response.BadRequest(c, err.Error()) + return + } + shared.OperationLogWithResourceID(c, h.audit, "retry-red-packet-refund", "red_packets", packetID, "success", "") + response.OK(c, gin.H{ + "packet": packetFromProto(resp.GetPacket()), + "refundedAmount": resp.GetRefundedAmount(), + "serverTime": resp.GetServerTimeMs(), + }) +} + func configFromProto(config *walletv1.RedPacketConfig) configDTO { if config == nil { return configDTO{} @@ -187,6 +264,7 @@ func configFromProto(config *walletv1.RedPacketConfig) configDTO { DelayedOpenSeconds: config.GetDelayedOpenSeconds(), ExpireSeconds: config.GetExpireSeconds(), DailySendLimit: config.GetDailySendLimit(), + RuleURL: config.GetRuleUrl(), UpdatedByAdminID: config.GetUpdatedByAdminId(), CreatedAtMS: config.GetCreatedAtMs(), UpdatedAtMS: config.GetUpdatedAtMs(), @@ -218,6 +296,9 @@ func packetFromProto(packet *walletv1.RedPacket) packetDTO { ExpiresAtMS: packet.GetExpiresAtMs(), CreatedAtMS: packet.GetCreatedAtMs(), UpdatedAtMS: packet.GetUpdatedAtMs(), + RefundAmount: packet.GetRefundAmount(), + RefundStatus: packet.GetRefundStatus(), + RefundedAtMS: packet.GetRefundedAtMs(), Claims: claims, } } @@ -241,6 +322,15 @@ func claimFromProto(claim *walletv1.RedPacketClaim) claimDTO { } } +func firstInt64Query(c *gin.Context, keys ...string) int64 { + for _, key := range keys { + if value := parseInt64Query(c, key); value > 0 { + return value + } + } + return 0 +} + func parseInt64Query(c *gin.Context, key string) int64 { value := strings.TrimSpace(c.Query(key)) if value == "" { diff --git a/server/admin/internal/modules/redpacket/routes.go b/server/admin/internal/modules/redpacket/routes.go index b87014a7..84fe5eeb 100644 --- a/server/admin/internal/modules/redpacket/routes.go +++ b/server/admin/internal/modules/redpacket/routes.go @@ -10,8 +10,9 @@ func RegisterRoutes(protected *gin.RouterGroup, h *Handler) { if h == nil { return } - protected.GET("/admin/activity/red-packets/config", middleware.RequirePermission("red-packet:view"), h.GetConfig) - protected.PUT("/admin/activity/red-packets/config", middleware.RequirePermission("red-packet:update"), h.UpdateConfig) - protected.GET("/admin/activity/red-packets", middleware.RequirePermission("red-packet:view"), h.ListPackets) - protected.GET("/admin/activity/red-packets/:packet_id", middleware.RequirePermission("red-packet:view"), h.GetPacket) + protected.GET("/resident-activity/voice-room-red-packet/config", middleware.RequirePermission("red-packet:view"), h.GetConfig) + protected.POST("/resident-activity/voice-room-red-packet/config", middleware.RequirePermission("red-packet:update"), h.UpdateConfig) + protected.GET("/resident-activity/voice-room-red-packet/records", middleware.RequirePermission("red-packet:view"), h.ListPackets) + protected.GET("/resident-activity/voice-room-red-packet/records/:packet_id", middleware.RequirePermission("red-packet:view"), h.GetPacket) + protected.POST("/resident-activity/voice-room-red-packet/refund/retry", middleware.RequirePermission("red-packet:update"), h.RetryRefund) } diff --git a/services/activity-service/internal/app/app.go b/services/activity-service/internal/app/app.go index 82694772..894a1d7e 100644 --- a/services/activity-service/internal/app/app.go +++ b/services/activity-service/internal/app/app.go @@ -235,7 +235,7 @@ func New(cfg config.Config) (*App, error) { if err != nil || !ok { return err } - return broadcastSvc.ConsumeRedPacketCreatedEvent(appcode.WithContext(ctx, event.AppCode), event) + return broadcastSvc.ConsumeRedPacketWalletEvent(appcode.WithContext(ctx, event.AppCode), event) }); err != nil { _ = consumer.Shutdown() shutdownConsumers(mqConsumers) @@ -436,25 +436,32 @@ func firstRechargeEventFromWalletMessage(body []byte) (firstrechargedomain.Recha }, true, nil } -func redPacketEventFromWalletMessage(body []byte) (broadcastdomain.RedPacketCreatedEvent, bool, error) { +func redPacketEventFromWalletMessage(body []byte) (broadcastdomain.RedPacketWalletEvent, bool, error) { message, err := walletmq.DecodeWalletOutboxMessage(body) if err != nil { - return broadcastdomain.RedPacketCreatedEvent{}, false, err + return broadcastdomain.RedPacketWalletEvent{}, false, err } - if message.EventType != "WalletRedPacketCreated" { - return broadcastdomain.RedPacketCreatedEvent{}, false, nil + if message.EventType != "WalletRedPacketCreated" && message.EventType != "WalletRedPacketClaimed" && message.EventType != "WalletRedPacketRefunded" { + return broadcastdomain.RedPacketWalletEvent{}, false, nil } - regionID := redPacketRegionIDFromWalletPayload(message.PayloadJSON) - if regionID <= 0 { - return broadcastdomain.RedPacketCreatedEvent{}, false, nil + packetID, packetType, roomID, regionID, status, openAtMS, expiresAtMS := redPacketFieldsFromWalletPayload(message.PayloadJSON, message.EventType) + if packetID == "" { + return broadcastdomain.RedPacketWalletEvent{}, false, nil } - return broadcastdomain.RedPacketCreatedEvent{ + return broadcastdomain.RedPacketWalletEvent{ AppCode: appcode.Normalize(message.AppCode), EventID: message.EventID, + EventType: message.EventType, TransactionID: message.TransactionID, CommandID: message.CommandID, PayloadJSON: message.PayloadJSON, + PacketID: packetID, + PacketType: packetType, + RoomID: roomID, RegionID: regionID, + Status: status, + OpenAtMS: openAtMS, + ExpiresAtMS: expiresAtMS, CreatedAtMS: message.OccurredAtMS, }, true, nil } @@ -480,19 +487,41 @@ func rechargeFactFromWalletPayload(payload string) (int64, string) { return sequence, rechargeType } -func redPacketRegionIDFromWalletPayload(payload string) int64 { +func redPacketFieldsFromWalletPayload(payload string, eventType string) (string, string, string, int64, string, int64, int64) { var decoded map[string]any if err := json.Unmarshal([]byte(payload), &decoded); err != nil { - return 0 + return "", "", "", 0, "", 0, 0 } - switch value := decoded["region_id"].(type) { + packetID := stringFromDecoded(decoded, "packet_id") + if packetID == "" { + packetID = stringFromDecoded(decoded, "packetId") + } + packetType := stringFromDecoded(decoded, "packet_type") + roomID := stringFromDecoded(decoded, "room_id") + regionID := int64FromDecoded(decoded, "region_id") + status := stringFromDecoded(decoded, "status") + openAtMS := int64FromDecoded(decoded, "open_at_ms") + expiresAtMS := int64FromDecoded(decoded, "expires_at_ms") + if eventType == "WalletRedPacketClaimed" || eventType == "WalletRedPacketRefunded" { + status = stringFromDecoded(decoded, "packet_status") + } + return packetID, packetType, roomID, regionID, status, openAtMS, expiresAtMS +} + +func stringFromDecoded(decoded map[string]any, key string) string { + value, _ := decoded[key].(string) + return value +} + +func int64FromDecoded(decoded map[string]any, key string) int64 { + switch value := decoded[key].(type) { case float64: return int64(value) case int64: return value case json.Number: - regionID, _ := value.Int64() - return regionID + parsed, _ := value.Int64() + return parsed default: return 0 } diff --git a/services/activity-service/internal/domain/broadcast/broadcast.go b/services/activity-service/internal/domain/broadcast/broadcast.go index 64eb3f98..1117fe9a 100644 --- a/services/activity-service/internal/domain/broadcast/broadcast.go +++ b/services/activity-service/internal/domain/broadcast/broadcast.go @@ -7,6 +7,8 @@ const ( ScopeGlobal = "global" // ScopeRegion 表示用户所在区域群;贵重礼物、区域红包、区域运营通知默认走这个范围。 ScopeRegion = "region" + // ScopeRoom 表示当前语音房 IM 群。 + ScopeRoom = "room" // TypeSuperGift 是贵重礼物播报,来源于 room-service 已提交的 RoomGiftSent 事实。 TypeSuperGift = "super_gift" @@ -74,13 +76,23 @@ type ConsumeRoomEventResult struct { BroadcastCreated bool } -// RedPacketCreatedEvent 是 wallet-service 已提交的红包创建事实。 -type RedPacketCreatedEvent struct { +// RedPacketWalletEvent 是 wallet-service 已提交的红包资金事实。 +type RedPacketWalletEvent struct { AppCode string EventID string + EventType string TransactionID string CommandID string PayloadJSON string + PacketID string + PacketType string + RoomID string RegionID int64 + Status string + OpenAtMS int64 + ExpiresAtMS int64 CreatedAtMS int64 } + +// RedPacketCreatedEvent keeps older tests and callers compiling while wallet red-packet facts are generalized. +type RedPacketCreatedEvent = RedPacketWalletEvent diff --git a/services/activity-service/internal/service/broadcast/red_packet_wallet_outbox.go b/services/activity-service/internal/service/broadcast/red_packet_wallet_outbox.go index 93d6ff81..b962cc9b 100644 --- a/services/activity-service/internal/service/broadcast/red_packet_wallet_outbox.go +++ b/services/activity-service/internal/service/broadcast/red_packet_wallet_outbox.go @@ -2,24 +2,110 @@ package broadcast import ( "context" + "encoding/json" + "strings" "hyapp/pkg/appcode" broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast" ) -// ConsumeRedPacketCreatedEvent converts one MQ-delivered wallet red-packet fact into a broadcast outbox. -func (s *Service) ConsumeRedPacketCreatedEvent(ctx context.Context, event broadcastdomain.RedPacketCreatedEvent) error { +// ConsumeRedPacketWalletEvent converts one MQ-delivered wallet red-packet fact into IM outbox records. +func (s *Service) ConsumeRedPacketWalletEvent(ctx context.Context, event broadcastdomain.RedPacketWalletEvent) error { if s == nil { return nil } - if event.RegionID <= 0 { + ctx = appcode.WithContext(ctx, event.AppCode) + switch event.EventType { + case "WalletRedPacketCreated": + return s.consumeRedPacketCreated(ctx, event) + case "WalletRedPacketClaimed": + if strings.EqualFold(event.Status, "finished") { + return s.publishRedPacketRoomEvent(ctx, event, "STATUS_CHANGED", event.CreatedAtMS) + } + case "WalletRedPacketRefunded": + return s.publishRedPacketRoomEvent(ctx, event, "STATUS_CHANGED", event.CreatedAtMS) + } + return nil +} + +func (s *Service) ConsumeRedPacketCreatedEvent(ctx context.Context, event broadcastdomain.RedPacketWalletEvent) error { + if event.EventType == "" { + event.EventType = "WalletRedPacketCreated" + } + return s.ConsumeRedPacketWalletEvent(ctx, event) +} + +func (s *Service) consumeRedPacketCreated(ctx context.Context, event broadcastdomain.RedPacketWalletEvent) error { + if event.RoomID == "" { return nil } - _, err := s.PublishRegionBroadcast(appcode.WithContext(ctx, event.AppCode), PublishInput{ + if err := s.publishRedPacketRoomEvent(ctx, event, "CREATED", event.CreatedAtMS); err != nil { + return err + } + if strings.EqualFold(event.PacketType, "delayed") && event.RegionID > 0 { + if _, err := s.PublishRegionBroadcast(ctx, PublishInput{ + EventID: event.EventID + ":region_created", + BroadcastType: broadcastdomain.TypeRedPacket, + RegionID: event.RegionID, + PayloadJSON: redPacketIMPayload(event, "CREATED", "REGION"), + }); err != nil { + return err + } + } + if strings.EqualFold(event.PacketType, "delayed") && event.OpenAtMS > 0 { + _, err := s.PublishRoomBroadcast(ctx, event.RoomID, PublishInput{ + EventID: event.EventID + ":open_due", + BroadcastType: broadcastdomain.TypeRedPacket, + RegionID: event.RegionID, + PayloadJSON: redPacketIMPayload(event, "OPEN_DUE", "ROOM"), + DeliverAtMS: event.OpenAtMS, + }) + return err + } + return nil +} + +func (s *Service) publishRedPacketRoomEvent(ctx context.Context, event broadcastdomain.RedPacketWalletEvent, eventName string, fallbackDeliverAtMS int64) error { + if event.RoomID == "" { + return nil + } + _, err := s.PublishRoomBroadcast(ctx, event.RoomID, PublishInput{ EventID: event.EventID, BroadcastType: broadcastdomain.TypeRedPacket, RegionID: event.RegionID, - PayloadJSON: event.PayloadJSON, + PayloadJSON: redPacketIMPayload(event, eventName, "ROOM"), + DeliverAtMS: fallbackDeliverAtMS, }) return err } + +func redPacketIMPayload(event broadcastdomain.RedPacketWalletEvent, eventName string, scope string) string { + packetMode := "IMMEDIATE" + if strings.EqualFold(event.PacketType, "delayed") { + packetMode = "DELAYED" + } + status := strings.ToUpper(strings.TrimSpace(event.Status)) + if status == "" { + status = "ACTIVE" + } + payload := map[string]any{ + "type": "ROOM_RED_PACKET", + "data": map[string]any{ + "event": eventName, + "scope": scope, + "packetNo": event.PacketID, + "roomId": event.RoomID, + "regionId": event.RegionID, + "packetMode": packetMode, + "status": status, + "claimStartTime": event.OpenAtMS, + "expireTime": event.ExpiresAtMS, + "serverTime": event.CreatedAtMS, + }, + } + encoded, err := json.Marshal(payload) + if err != nil { + return `{"type":"ROOM_RED_PACKET","data":{}}` + } + return string(encoded) +} diff --git a/services/activity-service/internal/service/broadcast/service.go b/services/activity-service/internal/service/broadcast/service.go index a9e80ded..e69f2bd6 100644 --- a/services/activity-service/internal/service/broadcast/service.go +++ b/services/activity-service/internal/service/broadcast/service.go @@ -65,6 +65,7 @@ type PublishInput struct { BroadcastType string RegionID int64 PayloadJSON string + DeliverAtMS int64 } // Service 拥有播报群建群补偿、播报入队和 outbox 投递决策。 @@ -146,6 +147,14 @@ func (s *Service) PublishRegionBroadcast(ctx context.Context, input PublishInput return s.enqueue(ctx, broadcastdomain.ScopeRegion, groupID, input) } +func (s *Service) PublishRoomBroadcast(ctx context.Context, roomID string, input PublishInput) (broadcastdomain.PublishResult, error) { + roomID = strings.TrimSpace(roomID) + if roomID == "" { + return broadcastdomain.PublishResult{}, xerr.New(xerr.InvalidArgument, "room_id is required") + } + return s.enqueue(ctx, broadcastdomain.ScopeRoom, roomID, input) +} + // PublishGlobalBroadcast 把全局播报写入持久化 outbox。 // 全局播报和区域播报共用同一张表,scope/group_id 决定最终投递目标。 func (s *Service) PublishGlobalBroadcast(ctx context.Context, input PublishInput) (broadcastdomain.PublishResult, error) { @@ -209,9 +218,13 @@ func (s *Service) enqueue(ctx context.Context, scope string, groupID string, inp BroadcastType: input.BroadcastType, PayloadJSON: payloadJSON, Status: broadcastdomain.StatusPending, + NextRetryAtMS: nowMS, CreatedAtMS: nowMS, UpdatedAtMS: nowMS, } + if input.DeliverAtMS > nowMS { + record.NextRetryAtMS = input.DeliverAtMS + } created, saved, err := s.repository.SaveBroadcastOutbox(ctx, record) if err != nil { return broadcastdomain.PublishResult{}, err diff --git a/services/gateway-service/internal/transport/http/httproutes/router.go b/services/gateway-service/internal/transport/http/httproutes/router.go index d583baf3..7ef19858 100644 --- a/services/gateway-service/internal/transport/http/httproutes/router.go +++ b/services/gateway-service/internal/transport/http/httproutes/router.go @@ -176,6 +176,14 @@ type WalletHandlers struct { CreateRoomRedPacket http.HandlerFunc GetRedPacket http.HandlerFunc ClaimRedPacket http.HandlerFunc + GetCurrentRegionIMGroup http.HandlerFunc + GetRedPacketIMGroup http.HandlerFunc + SendVoiceRoomRedPacket http.HandlerFunc + ListActiveRedPackets http.HandlerFunc + ClaimVoiceRoomRedPacket http.HandlerFunc + GetVoiceRoomRedPacket http.HandlerFunc + ListRedPacketClaims http.HandlerFunc + ListSentRedPackets http.HandlerFunc } type VIPHandlers struct { @@ -396,6 +404,15 @@ func (r routes) registerWalletRoutes() { r.profile("/rooms/{room_id}/red-packets/create", http.MethodPost, h.CreateRoomRedPacket) r.profile("/red-packets/{packet_id}", http.MethodGet, h.GetRedPacket) r.profile("/red-packets/{packet_id}/claim", http.MethodPost, h.ClaimRedPacket) + r.profile("/app/region-im-group/current", http.MethodGet, h.GetCurrentRegionIMGroup) + r.profile("/app/voice-room/red-packet/im-group", http.MethodGet, h.GetRedPacketIMGroup) + r.profile("/app/voice-room/red-packet/config", http.MethodGet, h.GetRedPacketConfig) + r.profile("/app/voice-room/red-packet/send", http.MethodPost, h.SendVoiceRoomRedPacket) + r.profile("/app/voice-room/red-packet/active", http.MethodGet, h.ListActiveRedPackets) + r.profile("/app/voice-room/red-packet/claim", http.MethodPost, h.ClaimVoiceRoomRedPacket) + r.profile("/app/voice-room/red-packet/detail", http.MethodGet, h.GetVoiceRoomRedPacket) + r.profile("/app/voice-room/red-packet/claim-records", http.MethodGet, h.ListRedPacketClaims) + r.profile("/app/voice-room/red-packet/sent-records", http.MethodGet, h.ListSentRedPackets) } func (r routes) registerVIPRoutes() { diff --git a/services/gateway-service/internal/transport/http/router.go b/services/gateway-service/internal/transport/http/router.go index 8c35629f..3938df06 100644 --- a/services/gateway-service/internal/transport/http/router.go +++ b/services/gateway-service/internal/transport/http/router.go @@ -39,6 +39,7 @@ func (h *Handler) Routes(jwtVerifier *auth.Verifier) http.Handler { }) walletAPI := walletapi.New(walletapi.Config{ WalletClient: h.walletClient, + RoomGuardClient: h.roomGuardClient, RoomQueryClient: h.roomQueryClient, UserIdentityClient: h.userIdentityClient, UserProfileClient: h.userProfileClient, diff --git a/services/gateway-service/internal/transport/http/walletapi/handler.go b/services/gateway-service/internal/transport/http/walletapi/handler.go index 47e07c1b..1cdf12f5 100644 --- a/services/gateway-service/internal/transport/http/walletapi/handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/handler.go @@ -11,6 +11,7 @@ import ( // It keeps wallet semantics behind wallet-service and only handles HTTP protocol conversion. type Handler struct { walletClient client.WalletClient + roomGuardClient client.RoomGuardClient roomQueryClient client.RoomQueryClient userIdentityClient client.UserIdentityClient userProfileClient client.UserProfileClient @@ -19,6 +20,7 @@ type Handler struct { type Config struct { WalletClient client.WalletClient + RoomGuardClient client.RoomGuardClient RoomQueryClient client.RoomQueryClient UserIdentityClient client.UserIdentityClient UserProfileClient client.UserProfileClient @@ -28,6 +30,7 @@ type Config struct { func New(config Config) *Handler { return &Handler{ walletClient: config.WalletClient, + roomGuardClient: config.RoomGuardClient, roomQueryClient: config.RoomQueryClient, userIdentityClient: config.UserIdentityClient, userProfileClient: config.UserProfileClient, @@ -52,6 +55,14 @@ func (h *Handler) WalletHandlers() httproutes.WalletHandlers { CreateRoomRedPacket: h.createRoomRedPacket, GetRedPacket: h.getRedPacket, ClaimRedPacket: h.claimRedPacket, + GetCurrentRegionIMGroup: h.getCurrentRegionIMGroup, + GetRedPacketIMGroup: h.getCurrentRegionIMGroup, + SendVoiceRoomRedPacket: h.sendVoiceRoomRedPacket, + ListActiveRedPackets: h.listActiveRedPackets, + ClaimVoiceRoomRedPacket: h.claimVoiceRoomRedPacket, + GetVoiceRoomRedPacket: h.getVoiceRoomRedPacket, + ListRedPacketClaims: h.listRedPacketClaims, + ListSentRedPackets: h.listSentRedPackets, } } diff --git a/services/gateway-service/internal/transport/http/walletapi/red_packet_handler.go b/services/gateway-service/internal/transport/http/walletapi/red_packet_handler.go index 562114f0..174207e5 100644 --- a/services/gateway-service/internal/transport/http/walletapi/red_packet_handler.go +++ b/services/gateway-service/internal/transport/http/walletapi/red_packet_handler.go @@ -1,14 +1,18 @@ package walletapi import ( + "fmt" "net/http" "strconv" "strings" roomv1 "hyapp.local/api/proto/room/v1" + userv1 "hyapp.local/api/proto/user/v1" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" + "hyapp/pkg/imgroup" "hyapp/pkg/roomid" + "hyapp/pkg/xerr" "hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/transport/http/httpkit" ) @@ -22,6 +26,7 @@ type redPacketConfigData struct { ExpireSeconds int32 `json:"expire_seconds"` DailySendLimit int32 `json:"daily_send_limit"` UpdatedByAdminID int64 `json:"updated_by_admin_id"` + RuleURL string `json:"rule_url"` CreatedAtMS int64 `json:"created_at_ms"` UpdatedAtMS int64 `json:"updated_at_ms"` } @@ -113,6 +118,377 @@ func (b claimRedPacketRequestBody) normalizedCommandID() string { return strings.TrimSpace(b.CommandIDCamel) } +type voiceRoomRedPacketSendBody struct { + RoomID string `json:"roomId"` + RoomIDSnake string `json:"room_id"` + PacketMode string `json:"packetMode"` + PacketType string `json:"packet_type"` + TotalAmount int64 `json:"totalAmount"` + TotalAmountV2 int64 `json:"total_amount"` + TotalCount int32 `json:"totalCount"` + PacketCount int32 `json:"packet_count"` + RequestID string `json:"requestId"` + RequestIDV2 string `json:"request_id"` +} + +func (b voiceRoomRedPacketSendBody) roomID() string { + if value := strings.TrimSpace(b.RoomID); value != "" { + return value + } + return strings.TrimSpace(b.RoomIDSnake) +} + +func (b voiceRoomRedPacketSendBody) mode() string { + if value := strings.TrimSpace(b.PacketMode); value != "" { + return value + } + return strings.TrimSpace(b.PacketType) +} + +func (b voiceRoomRedPacketSendBody) amount() int64 { + if b.TotalAmount > 0 { + return b.TotalAmount + } + return b.TotalAmountV2 +} + +func (b voiceRoomRedPacketSendBody) count() int32 { + if b.TotalCount > 0 { + return b.TotalCount + } + return b.PacketCount +} + +func (b voiceRoomRedPacketSendBody) requestID() string { + if value := strings.TrimSpace(b.RequestID); value != "" { + return value + } + return strings.TrimSpace(b.RequestIDV2) +} + +type voiceRoomRedPacketClaimBody struct { + PacketNo string `json:"packetNo"` + PacketID string `json:"packet_id"` + RequestID string `json:"requestId"` +} + +func (b voiceRoomRedPacketClaimBody) packetNo() string { + if value := strings.TrimSpace(b.PacketNo); value != "" { + return value + } + return strings.TrimSpace(b.PacketID) +} + +func (h *Handler) getCurrentRegionIMGroup(writer http.ResponseWriter, request *http.Request) { + if h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + user, ok := h.currentUser(writer, request) + if !ok { + return + } + if user.GetRegionId() <= 0 { + httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_region_missing", "conflict") + return + } + groupID, err := imgroup.RegionBroadcastGroupID(appcode.FromContext(request.Context()), user.GetRegionId()) + if err != nil { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "regionId": user.GetRegionId(), + "regionCode": user.GetRegionCode(), + "regionName": user.GetRegionName(), + "groupId": groupID, + "groupName": groupID, + }) +} + +func (h *Handler) sendVoiceRoomRedPacket(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil || h.roomQueryClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body voiceRoomRedPacketSendBody + if !httpkit.Decode(writer, request, &body) { + return + } + roomID := body.roomID() + packetType := internalRedPacketType(body.mode()) + requestID := body.requestID() + if !roomid.ValidStringID(roomID) || packetType == "" || body.amount() <= 0 || body.count() <= 0 || requestID == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + user, ok := h.currentUser(writer, request) + if !ok { + return + } + if user.GetRegionId() <= 0 { + httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_region_missing", "conflict") + return + } + snapshotResp, err := h.roomQueryClient.GetRoomSnapshot(request.Context(), &roomv1.GetRoomSnapshotRequest{ + Meta: httpkit.RoomMeta(request, roomID, ""), + RoomId: roomID, + ViewerUserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + regionID := snapshotResp.GetRoom().GetVisibleRegionId() + if regionID <= 0 { + httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_region_missing", "conflict") + return + } + if regionID != user.GetRegionId() { + httpkit.WriteError(writer, request, http.StatusForbidden, "voice_room_region_not_match", "permission denied") + return + } + commandID := fmt.Sprintf("red_packet_send:%d:%s", auth.UserIDFromContext(request.Context()), requestID) + resp, err := h.walletClient.CreateRedPacket(request.Context(), &walletv1.CreateRedPacketRequest{ + RequestId: requestID, + AppCode: appcode.FromContext(request.Context()), + CommandId: commandID, + SenderUserId: auth.UserIDFromContext(request.Context()), + RoomId: roomID, + RegionId: regionID, + PacketType: packetType, + TotalAmount: body.amount(), + PacketCount: body.count(), + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "packet": voiceRoomPacketFromProto(resp.GetPacket()), + "balanceAfter": resp.GetBalanceAfter(), + "serverTime": resp.GetServerTimeMs(), + }) +} + +func (h *Handler) listActiveRedPackets(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + roomID := strings.TrimSpace(request.URL.Query().Get("roomId")) + if roomID == "" { + roomID = strings.TrimSpace(request.URL.Query().Get("room_id")) + } + if !roomid.ValidStringID(roomID) { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.ListRedPackets(request.Context(), &walletv1.ListRedPacketsRequest{ + AppCode: appcode.FromContext(request.Context()), + RoomId: roomID, + ViewerUserId: auth.UserIDFromContext(request.Context()), + Page: 1, + PageSize: 100, + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + packets := make([]map[string]any, 0, len(resp.GetPackets())) + for _, packet := range resp.GetPackets() { + if packet.GetStatus() == "active" || packet.GetStatus() == "waiting_open" { + packets = append(packets, voiceRoomPacketFromProto(packet)) + } + } + httpkit.WriteOK(writer, request, map[string]any{ + "packets": packets, + "serverTime": resp.GetServerTimeMs(), + }) +} + +func (h *Handler) claimVoiceRoomRedPacket(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil || h.roomGuardClient == nil || h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + var body voiceRoomRedPacketClaimBody + if !httpkit.Decode(writer, request, &body) { + return + } + packetNo := body.packetNo() + if packetNo == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + packetResp, err := h.walletClient.GetRedPacket(request.Context(), &walletv1.GetRedPacketRequest{ + AppCode: appcode.FromContext(request.Context()), + PacketId: packetNo, + ViewerUserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + packet := packetResp.GetPacket() + if !h.verifyRedPacketRoomPresence(writer, request, packet.GetRoomId()) { + return + } + user, ok := h.currentUser(writer, request) + if !ok { + return + } + if user.GetRegionId() <= 0 { + httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_region_missing", "conflict") + return + } + if packet.GetRegionId() > 0 && packet.GetRegionId() != user.GetRegionId() { + httpkit.WriteError(writer, request, http.StatusForbidden, "voice_room_region_not_match", "permission denied") + return + } + commandID := fmt.Sprintf("red_packet_claim:%s:%d", packetNo, auth.UserIDFromContext(request.Context())) + resp, err := h.walletClient.ClaimRedPacket(request.Context(), &walletv1.ClaimRedPacketRequest{ + AppCode: appcode.FromContext(request.Context()), + CommandId: commandID, + PacketId: packetNo, + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "claim": voiceRoomClaimFromProto(resp.GetClaim()), + "packet": voiceRoomPacketFromProto(resp.GetPacket()), + "balanceAfter": resp.GetBalanceAfter(), + "serverTime": resp.GetServerTimeMs(), + }) +} + +func (h *Handler) getVoiceRoomRedPacket(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + packetNo := strings.TrimSpace(request.URL.Query().Get("packetNo")) + if packetNo == "" { + packetNo = strings.TrimSpace(request.URL.Query().Get("packet_id")) + } + if packetNo == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.GetRedPacket(request.Context(), &walletv1.GetRedPacketRequest{ + AppCode: appcode.FromContext(request.Context()), + PacketId: packetNo, + ViewerUserId: auth.UserIDFromContext(request.Context()), + IncludeClaims: true, + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + httpkit.WriteOK(writer, request, map[string]any{ + "packet": voiceRoomPacketFromProto(resp.GetPacket()), + "serverTime": resp.GetServerTimeMs(), + }) +} + +func (h *Handler) listRedPacketClaims(writer http.ResponseWriter, request *http.Request) { + packetNo := strings.TrimSpace(request.URL.Query().Get("packetNo")) + if packetNo == "" { + packetNo = strings.TrimSpace(request.URL.Query().Get("packet_id")) + } + if packetNo == "" { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + page, ok := httpkit.PositiveInt32Query(request, "page", 1) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + pageSize, ok := httpkit.PositiveInt32Query(request, "pageSize", 20) + if !ok { + pageSize, ok = httpkit.PositiveInt32Query(request, "page_size", 20) + } + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.GetRedPacket(request.Context(), &walletv1.GetRedPacketRequest{ + AppCode: appcode.FromContext(request.Context()), + PacketId: packetNo, + ViewerUserId: auth.UserIDFromContext(request.Context()), + IncludeClaims: true, + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + all := resp.GetPacket().GetClaims() + start := int((page - 1) * pageSize) + end := start + int(pageSize) + if start > len(all) { + start = len(all) + } + if end > len(all) { + end = len(all) + } + items := make([]map[string]any, 0, end-start) + for _, claim := range all[start:end] { + items = append(items, voiceRoomClaimFromProto(claim)) + } + httpkit.WriteOK(writer, request, map[string]any{ + "items": items, + "total": len(all), + "page": page, + "pageSize": pageSize, + "serverTime": resp.GetServerTimeMs(), + }) +} + +func (h *Handler) listSentRedPackets(writer http.ResponseWriter, request *http.Request) { + if h.walletClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return + } + page, ok := httpkit.PositiveInt32Query(request, "page", 1) + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + pageSize, ok := httpkit.PositiveInt32Query(request, "pageSize", 20) + if !ok { + pageSize, ok = httpkit.PositiveInt32Query(request, "page_size", 20) + } + if !ok { + httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument") + return + } + resp, err := h.walletClient.ListRedPackets(request.Context(), &walletv1.ListRedPacketsRequest{ + AppCode: appcode.FromContext(request.Context()), + SenderUserId: auth.UserIDFromContext(request.Context()), + Page: page, + PageSize: pageSize, + }) + if err != nil { + writeRedPacketRPCError(writer, request, err) + return + } + items := make([]map[string]any, 0, len(resp.GetPackets())) + for _, packet := range resp.GetPackets() { + items = append(items, voiceRoomPacketFromProto(packet)) + } + httpkit.WriteOK(writer, request, map[string]any{ + "items": items, + "total": resp.GetTotal(), + "page": page, + "pageSize": pageSize, + "serverTime": resp.GetServerTimeMs(), + }) +} + func (h *Handler) getRedPacketConfig(writer http.ResponseWriter, request *http.Request) { if h.walletClient == nil { httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") @@ -305,6 +681,7 @@ func redPacketConfigFromProto(config *walletv1.RedPacketConfig) redPacketConfigD ExpireSeconds: config.GetExpireSeconds(), DailySendLimit: config.GetDailySendLimit(), UpdatedByAdminID: config.GetUpdatedByAdminId(), + RuleURL: config.GetRuleUrl(), CreatedAtMS: config.GetCreatedAtMs(), UpdatedAtMS: config.GetUpdatedAtMs(), } @@ -376,3 +753,165 @@ func boolQuery(request *http.Request, name string) bool { parsed, err := strconv.ParseBool(value) return err == nil && parsed } + +func (h *Handler) currentUser(writer http.ResponseWriter, request *http.Request) (*userv1.User, bool) { + if h.userProfileClient == nil { + httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") + return nil, false + } + resp, err := h.userProfileClient.GetUser(request.Context(), &userv1.GetUserRequest{ + Meta: httpkit.UserMeta(request, ""), + UserId: auth.UserIDFromContext(request.Context()), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return nil, false + } + user := resp.GetUser() + if user == nil { + httpkit.WriteError(writer, request, http.StatusInternalServerError, httpkit.CodeInternalError, "internal error") + return nil, false + } + return user, true +} + +func (h *Handler) verifyRedPacketRoomPresence(writer http.ResponseWriter, request *http.Request, roomID string) bool { + if !roomid.ValidStringID(roomID) { + httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_red_packet_room_presence_required", "conflict") + return false + } + resp, err := h.roomGuardClient.VerifyRoomPresence(request.Context(), &roomv1.VerifyRoomPresenceRequest{ + RoomId: roomID, + UserId: auth.UserIDFromContext(request.Context()), + RequestId: httpkit.RequestIDFromContext(request.Context()), + AppCode: appcode.FromContext(request.Context()), + }) + if err != nil { + httpkit.WriteRPCError(writer, request, err) + return false + } + if !resp.GetPresent() { + httpkit.WriteError(writer, request, http.StatusConflict, "voice_room_red_packet_room_presence_required", "conflict") + return false + } + return true +} + +func writeRedPacketRPCError(writer http.ResponseWriter, request *http.Request, err error) { + if err == nil { + return + } + reason := xerr.ReasonFromGRPC(err) + statusCode, code, message := httpkit.MapReasonToHTTP(reason) + switch reason { + case xerr.RedPacketDisabled: + code = "voice_room_red_packet_disabled" + case xerr.RedPacketInvalidAmountTier, xerr.RedPacketAmountTooSmall: + code = "voice_room_red_packet_amount_invalid" + case xerr.RedPacketInvalidCountTier: + code = "voice_room_red_packet_count_invalid" + case xerr.RedPacketDailyLimitReached: + code = "voice_room_red_packet_daily_limit_exceeded" + case xerr.InsufficientBalance: + code = "voice_room_red_packet_insufficient_balance" + case xerr.NotFound: + code = "voice_room_red_packet_not_found" + case xerr.RedPacketNotOpen: + code = "voice_room_red_packet_not_started" + case xerr.RedPacketExpired: + code = "voice_room_red_packet_expired" + case xerr.RedPacketSoldOut: + code = "voice_room_red_packet_finished" + case xerr.LedgerConflict: + code = "claim_processing" + } + httpkit.WriteError(writer, request, statusCode, code, message) +} + +func internalRedPacketType(mode string) string { + switch strings.ToUpper(strings.TrimSpace(mode)) { + case "IMMEDIATE": + return "normal" + case "DELAYED": + return "delayed" + } + switch strings.ToLower(strings.TrimSpace(mode)) { + case "normal": + return "normal" + case "delayed": + return "delayed" + default: + return "" + } +} + +func publicRedPacketMode(packetType string) string { + switch strings.ToLower(strings.TrimSpace(packetType)) { + case "normal": + return "IMMEDIATE" + case "delayed": + return "DELAYED" + default: + return strings.ToUpper(strings.TrimSpace(packetType)) + } +} + +func publicRedPacketStatus(status string) string { + switch strings.ToLower(strings.TrimSpace(status)) { + case "waiting_open": + return "WAITING_OPEN" + case "active": + return "ACTIVE" + case "finished": + return "FINISHED" + case "refunded": + return "REFUNDED" + default: + return strings.ToUpper(strings.TrimSpace(status)) + } +} + +func voiceRoomPacketFromProto(packet *walletv1.RedPacket) map[string]any { + if packet == nil { + return map[string]any{} + } + claims := make([]map[string]any, 0, len(packet.GetClaims())) + for _, claim := range packet.GetClaims() { + claims = append(claims, voiceRoomClaimFromProto(claim)) + } + return map[string]any{ + "packetNo": packet.GetPacketId(), + "roomId": packet.GetRoomId(), + "regionId": packet.GetRegionId(), + "senderUserId": packet.GetSenderUserId(), + "packetMode": publicRedPacketMode(packet.GetPacketType()), + "totalAmount": packet.GetTotalAmount(), + "totalCount": packet.GetPacketCount(), + "remainingAmount": packet.GetRemainingAmount(), + "remainingCount": packet.GetRemainingCount(), + "status": publicRedPacketStatus(packet.GetStatus()), + "claimStartTime": packet.GetOpenAtMs(), + "expireTime": packet.GetExpiresAtMs(), + "createdAt": packet.GetCreatedAtMs(), + "claimedByMe": packet.GetClaimedByViewer(), + "myClaimAmount": packet.GetViewerClaimAmount(), + "refundAmount": packet.GetRefundAmount(), + "refundStatus": packet.GetRefundStatus(), + "refundedAt": packet.GetRefundedAtMs(), + "claims": claims, + } +} + +func voiceRoomClaimFromProto(claim *walletv1.RedPacketClaim) map[string]any { + if claim == nil { + return map[string]any{} + } + return map[string]any{ + "claimId": claim.GetClaimId(), + "packetNo": claim.GetPacketId(), + "userId": claim.GetUserId(), + "amount": claim.GetAmount(), + "status": claim.GetStatus(), + "createdAt": claim.GetCreatedAtMs(), + } +} diff --git a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql index 9ba48965..3c8ea983 100644 --- a/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql +++ b/services/wallet-service/deploy/mysql/initdb/001_wallet_service.sql @@ -666,8 +666,9 @@ CREATE TABLE IF NOT EXISTS red_packet_configs ( app_code VARCHAR(32) NOT NULL DEFAULT 'lalu' COMMENT '应用编码,用于多租户隔离', enabled BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否启用红包', delayed_open_seconds INT NOT NULL DEFAULT 0 COMMENT '延迟红包打开秒数', - expire_seconds INT NOT NULL DEFAULT 300 COMMENT '红包过期秒数', + expire_seconds INT NOT NULL DEFAULT 86400 COMMENT '红包过期秒数,房内红包固定 24 小时', daily_send_limit INT NOT NULL DEFAULT 0 COMMENT '每用户 UTC 日发送上限,0 表示禁止发送', + rule_url VARCHAR(512) NOT NULL DEFAULT '' COMMENT '红包规则说明链接', updated_by_admin_id BIGINT NOT NULL DEFAULT 0 COMMENT '最后更新后台用户 ID', created_at_ms BIGINT NOT NULL COMMENT '创建时间,UTC epoch ms', updated_at_ms BIGINT NOT NULL COMMENT '更新时间,UTC epoch ms', diff --git a/services/wallet-service/internal/domain/ledger/ledger.go b/services/wallet-service/internal/domain/ledger/ledger.go index 2b5ac24b..19683a27 100644 --- a/services/wallet-service/internal/domain/ledger/ledger.go +++ b/services/wallet-service/internal/domain/ledger/ledger.go @@ -80,6 +80,9 @@ const ( RedPacketStatusRefunded = "refunded" // RedPacketClaimStatusClaimed 表示红包份额领取成功。 RedPacketClaimStatusClaimed = "claimed" + + // RedPacketExpireSeconds 固定房内红包 24 小时过期退款。 + RedPacketExpireSeconds int32 = 24 * 60 * 60 ) // DebitGiftCommand 是 room-service 送礼扣费的账务命令。 @@ -606,6 +609,7 @@ type RedPacketConfig struct { ExpireSeconds int32 DailySendLimit int32 UpdatedByAdminID int64 + RuleURL string CreatedAtMS int64 UpdatedAtMS int64 } @@ -631,6 +635,9 @@ type RedPacket struct { ClaimedByViewer bool ViewerClaimAmount int64 Claims []RedPacketClaim + RefundAmount int64 + RefundStatus string + RefundedAtMS int64 } // RedPacketClaim 是单个用户抢红包的到账事实。 @@ -673,9 +680,15 @@ type RedPacketClaimCommand struct { type RedPacketClaimReceipt struct { Claim RedPacketClaim + Packet RedPacket BalanceAfter int64 } +type RedPacketRefundReceipt struct { + Packet RedPacket + RefundedAmount int64 +} + type RedPacketQuery struct { AppCode string RoomID string @@ -796,6 +809,12 @@ func NormalizeGameOpType(opType string) string { } func NormalizeRedPacketType(packetType string) string { + switch strings.ToUpper(strings.TrimSpace(packetType)) { + case "IMMEDIATE": + return RedPacketTypeNormal + case "DELAYED": + return RedPacketTypeDelayed + } switch strings.ToLower(strings.TrimSpace(packetType)) { case RedPacketTypeNormal: return RedPacketTypeNormal diff --git a/services/wallet-service/internal/service/wallet/red_packet.go b/services/wallet-service/internal/service/wallet/red_packet.go index 6819a692..c0a482f7 100644 --- a/services/wallet-service/internal/service/wallet/red_packet.go +++ b/services/wallet-service/internal/service/wallet/red_packet.go @@ -28,18 +28,23 @@ func (s *Service) UpdateRedPacketConfig(ctx context.Context, config ledger.RedPa config.AppCode = appcode.Normalize(config.AppCode) config.CountTiers = normalizeCountTiers(config.CountTiers) config.AmountTiers = normalizeAmountTiers(config.AmountTiers) + config.RuleURL = strings.TrimSpace(config.RuleURL) + config.ExpireSeconds = ledger.RedPacketExpireSeconds if config.UpdatedByAdminID <= 0 { return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "operator_user_id is required") } if len(config.CountTiers) == 0 || len(config.AmountTiers) == 0 { return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet tiers are required") } - if config.DelayedOpenSeconds < 0 || config.ExpireSeconds <= 0 || config.DailySendLimit < 0 { + if config.DelayedOpenSeconds < 0 || config.DailySendLimit < 0 { return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet config is invalid") } - if config.DelayedOpenSeconds >= config.ExpireSeconds { + if config.DelayedOpenSeconds >= ledger.RedPacketExpireSeconds { return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet expire_seconds must be greater than delayed_open_seconds") } + if len(config.CountTiers) > 0 && len(config.AmountTiers) > 0 && config.AmountTiers[0] < int64(config.CountTiers[len(config.CountTiers)-1]) { + return ledger.RedPacketConfig{}, xerr.New(xerr.InvalidArgument, "red packet amount tiers must cover count tiers") + } ctx = appcode.WithContext(ctx, config.AppCode) return s.repository.UpdateRedPacketConfig(ctx, config) } @@ -127,6 +132,19 @@ func (s *Service) ExpireRedPackets(ctx context.Context, appCode string, batchSiz return s.repository.ExpireRedPackets(ctx, appCode, batchSize) } +func (s *Service) RetryRedPacketRefund(ctx context.Context, appCode string, packetID string, operatorUserID int64) (ledger.RedPacketRefundReceipt, error) { + if s.repository == nil { + return ledger.RedPacketRefundReceipt{}, xerr.New(xerr.Unavailable, "wallet repository is not configured") + } + packetID = strings.TrimSpace(packetID) + if packetID == "" || operatorUserID <= 0 { + return ledger.RedPacketRefundReceipt{}, xerr.New(xerr.InvalidArgument, "red packet refund retry command is incomplete") + } + appCode = appcode.Normalize(appCode) + ctx = appcode.WithContext(ctx, appCode) + return s.repository.RetryRedPacketRefund(ctx, appCode, packetID, operatorUserID) +} + func normalizeCountTiers(values []int32) []int32 { seen := make(map[int32]bool, len(values)) out := make([]int32, 0, len(values)) diff --git a/services/wallet-service/internal/service/wallet/service.go b/services/wallet-service/internal/service/wallet/service.go index 99387511..0ae7d828 100644 --- a/services/wallet-service/internal/service/wallet/service.go +++ b/services/wallet-service/internal/service/wallet/service.go @@ -32,6 +32,7 @@ type Repository interface { ListRedPackets(ctx context.Context, query ledger.RedPacketQuery) ([]ledger.RedPacket, int64, error) GetRedPacket(ctx context.Context, appCode string, packetID string, viewerUserID int64, includeClaims bool) (ledger.RedPacket, error) ExpireRedPackets(ctx context.Context, appCode string, batchSize int32) (ledger.RedPacketExpireResult, error) + RetryRedPacketRefund(ctx context.Context, appCode string, packetID string, operatorUserID int64) (ledger.RedPacketRefundReceipt, error) ListRechargeBills(ctx context.Context, query ledger.ListRechargeBillsQuery) ([]ledger.RechargeBill, int64, error) GetWalletOverview(ctx context.Context, userID int64) (ledger.WalletOverview, error) GetWalletValueSummary(ctx context.Context, userID int64) (ledger.WalletValueSummary, error) diff --git a/services/wallet-service/internal/storage/mysql/red_packet_repository.go b/services/wallet-service/internal/storage/mysql/red_packet_repository.go index 7856f080..8cd0d2f2 100644 --- a/services/wallet-service/internal/storage/mysql/red_packet_repository.go +++ b/services/wallet-service/internal/storage/mysql/red_packet_repository.go @@ -58,6 +58,12 @@ type redPacketClaimMetadata struct { PacketID string `json:"packet_id"` UserID int64 `json:"user_id"` Amount int64 `json:"amount"` + RoomID string `json:"room_id"` + RegionID int64 `json:"region_id"` + PacketType string `json:"packet_type"` + RemainingAmount int64 `json:"remaining_amount"` + RemainingCount int32 `json:"remaining_count"` + PacketStatus string `json:"packet_status"` BalanceAfter int64 `json:"balance_after"` WalletTransaction string `json:"wallet_transaction_id"` ClaimedAtMS int64 `json:"claimed_at_ms"` @@ -69,7 +75,11 @@ type redPacketRefundMetadata struct { CommandID string `json:"command_id"` PacketID string `json:"packet_id"` SenderUserID int64 `json:"sender_user_id"` + RoomID string `json:"room_id"` + RegionID int64 `json:"region_id"` + PacketType string `json:"packet_type"` RefundAmount int64 `json:"refund_amount"` + PacketStatus string `json:"packet_status"` BalanceAfter int64 `json:"balance_after"` WalletTransaction string `json:"wallet_transaction_id"` RefundedAtMS int64 `json:"refunded_at_ms"` @@ -110,21 +120,23 @@ func (r *Repository) UpdateRedPacketConfig(ctx context.Context, config ledger.Re if _, err := tx.ExecContext(ctx, ` INSERT INTO red_packet_configs ( - app_code, enabled, delayed_open_seconds, expire_seconds, daily_send_limit, + app_code, enabled, delayed_open_seconds, expire_seconds, daily_send_limit, rule_url, updated_by_admin_id, created_at_ms, updated_at_ms - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE enabled = VALUES(enabled), delayed_open_seconds = VALUES(delayed_open_seconds), expire_seconds = VALUES(expire_seconds), daily_send_limit = VALUES(daily_send_limit), + rule_url = VALUES(rule_url), updated_by_admin_id = VALUES(updated_by_admin_id), updated_at_ms = VALUES(updated_at_ms)`, config.AppCode, config.Enabled, config.DelayedOpenSeconds, - config.ExpireSeconds, + ledger.RedPacketExpireSeconds, config.DailySendLimit, + strings.TrimSpace(config.RuleURL), config.UpdatedByAdminID, nowMS, nowMS, @@ -202,7 +214,7 @@ func (r *Repository) CreateRedPacket(ctx context.Context, command ledger.RedPack if command.TotalAmount < int64(command.PacketCount) { return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.RedPacketAmountTooSmall, "red packet amount is too small") } - if config.ExpireSeconds <= 0 || (command.PacketType == ledger.RedPacketTypeDelayed && config.ExpireSeconds <= config.DelayedOpenSeconds) { + if ledger.RedPacketExpireSeconds <= 0 || (command.PacketType == ledger.RedPacketTypeDelayed && ledger.RedPacketExpireSeconds <= config.DelayedOpenSeconds) { return ledger.RedPacketCreateReceipt{}, xerr.New(xerr.InvalidArgument, "red packet config time range is invalid") } if err := r.incrementRedPacketDailyCounter(ctx, tx, command.SenderUserID, config.DailySendLimit, nowMS); err != nil { @@ -229,7 +241,7 @@ func (r *Repository) CreateRedPacket(ctx context.Context, command ledger.RedPack openAtMS = nowMS + int64(config.DelayedOpenSeconds)*1000 status = ledger.RedPacketStatusWaitingOpen } - expiresAtMS := nowMS + int64(config.ExpireSeconds)*1000 + expiresAtMS := nowMS + int64(ledger.RedPacketExpireSeconds)*1000 balanceAfter := sender.AvailableAmount - command.TotalAmount metadata := redPacketMetadata{ AppCode: command.AppCode, @@ -322,11 +334,11 @@ func (r *Repository) ClaimRedPacket(ctx context.Context, command ledger.RedPacke if nowMS < packet.OpenAtMS { return ledger.RedPacketClaimReceipt{}, xerr.New(xerr.RedPacketNotOpen, "red packet is not open") } - if claimed, err := r.userClaimedRedPacket(ctx, tx, command.PacketID, command.UserID); err != nil || claimed { + if existingClaim, claimed, err := r.getUserRedPacketClaim(ctx, tx, command.PacketID, command.UserID); err != nil || claimed { if err != nil { return ledger.RedPacketClaimReceipt{}, err } - return ledger.RedPacketClaimReceipt{}, xerr.New(xerr.RedPacketAlreadyClaimed, "red packet already claimed") + return ledger.RedPacketClaimReceipt{Claim: existingClaim, Packet: packet}, nil } allocationNo, amount, err := r.lockOneRedPacketAllocation(ctx, tx, command.PacketID) if err != nil { @@ -350,6 +362,11 @@ func (r *Repository) ClaimRedPacket(ctx context.Context, command ledger.RedPacke PacketID: command.PacketID, UserID: command.UserID, Amount: amount, + RoomID: packet.RoomID, + RegionID: packet.RegionID, + PacketType: packet.PacketType, + RemainingAmount: packet.RemainingAmount - amount, + RemainingCount: packet.RemainingCount - 1, BalanceAfter: balanceAfter, WalletTransaction: transactionID, ClaimedAtMS: nowMS, @@ -376,7 +393,14 @@ func (r *Repository) ClaimRedPacket(ctx context.Context, command ledger.RedPacke } if err := r.insertRedPacketClaim(ctx, tx, metadata); err != nil { if isMySQLDuplicateError(err) { - return ledger.RedPacketClaimReceipt{}, xerr.New(xerr.RedPacketAlreadyClaimed, "red packet already claimed") + existingClaim, claimed, lookupErr := r.getUserRedPacketClaim(ctx, tx, command.PacketID, command.UserID) + if lookupErr != nil { + return ledger.RedPacketClaimReceipt{}, lookupErr + } + if claimed { + return r.redPacketClaimReceiptForTransaction(ctx, tx, existingClaim.WalletTransactionID) + } + return ledger.RedPacketClaimReceipt{}, xerr.New(xerr.LedgerConflict, "red packet claim already exists") } return ledger.RedPacketClaimReceipt{}, err } @@ -395,6 +419,7 @@ func (r *Repository) ClaimRedPacket(ctx context.Context, command ledger.RedPacke if newRemainingCount == 0 { newStatus = ledger.RedPacketStatusFinished } + metadata.PacketStatus = newStatus if _, err := tx.ExecContext(ctx, ` UPDATE red_packets SET remaining_amount = ?, remaining_count = ?, status = ?, updated_at_ms = ? @@ -412,7 +437,11 @@ func (r *Repository) ClaimRedPacket(ctx context.Context, command ledger.RedPacke if err := tx.Commit(); err != nil { return ledger.RedPacketClaimReceipt{}, err } - return ledger.RedPacketClaimReceipt{Claim: redPacketClaimFromMetadata(metadata), BalanceAfter: balanceAfter}, nil + packet.RemainingAmount = newRemainingAmount + packet.RemainingCount = newRemainingCount + packet.Status = newStatus + packet.UpdatedAtMS = nowMS + return ledger.RedPacketClaimReceipt{Claim: redPacketClaimFromMetadata(metadata), Packet: packet, BalanceAfter: balanceAfter}, nil } func (r *Repository) ListRedPackets(ctx context.Context, query ledger.RedPacketQuery) ([]ledger.RedPacket, int64, error) { @@ -458,6 +487,9 @@ func (r *Repository) ListRedPackets(ctx context.Context, query ledger.RedPacketQ if err := rows.Err(); err != nil { return nil, 0, err } + if err := r.attachRefunds(ctx, packets); err != nil { + return nil, 0, err + } if query.ViewerUserID > 0 { if err := r.attachViewerClaims(ctx, packets, query.ViewerUserID); err != nil { return nil, 0, err @@ -480,6 +512,15 @@ func (r *Repository) GetRedPacket(ctx context.Context, app string, packetID stri if err := r.attachViewerClaims(ctx, packets, viewerUserID); err != nil { return ledger.RedPacket{}, err } + if err := r.attachRefunds(ctx, packets); err != nil { + return ledger.RedPacket{}, err + } + packet = packets[0] + } else { + packets := []ledger.RedPacket{packet} + if err := r.attachRefunds(ctx, packets); err != nil { + return ledger.RedPacket{}, err + } packet = packets[0] } if includeClaims { @@ -541,6 +582,30 @@ func (r *Repository) ExpireRedPackets(ctx context.Context, app string, batchSize return result, nil } +func (r *Repository) RetryRedPacketRefund(ctx context.Context, app string, packetID string, operatorUserID int64) (ledger.RedPacketRefundReceipt, error) { + if r == nil || r.db == nil { + return ledger.RedPacketRefundReceipt{}, xerr.New(xerr.Unavailable, "mysql repository is not configured") + } + _ = operatorUserID + ctx = contextWithCommandApp(ctx, app) + nowMS := time.Now().UTC().UnixMilli() + refunded, amount, err := r.expireOneRedPacket(ctx, strings.TrimSpace(packetID), nowMS) + if err != nil { + return ledger.RedPacketRefundReceipt{}, err + } + packet, err := r.GetRedPacket(ctx, appcode.FromContext(ctx), packetID, 0, false) + if err != nil { + return ledger.RedPacketRefundReceipt{}, err + } + if !refunded { + if packet.Status == ledger.RedPacketStatusRefunded { + return ledger.RedPacketRefundReceipt{Packet: packet, RefundedAmount: packet.RefundAmount}, nil + } + return ledger.RedPacketRefundReceipt{}, xerr.New(xerr.RedPacketExpired, "red packet is not expired") + } + return ledger.RedPacketRefundReceipt{Packet: packet, RefundedAmount: amount}, nil +} + func (r *Repository) expireOneRedPacket(ctx context.Context, packetID string, nowMS int64) (bool, int64, error) { tx, err := r.db.BeginTx(ctx, nil) if err != nil { @@ -588,7 +653,11 @@ func (r *Repository) expireOneRedPacket(ctx context.Context, packetID string, no CommandID: commandID, PacketID: packetID, SenderUserID: packet.SenderUserID, + RoomID: packet.RoomID, + RegionID: packet.RegionID, + PacketType: packet.PacketType, RefundAmount: refundAmount, + PacketStatus: ledger.RedPacketStatusRefunded, BalanceAfter: balanceAfter, WalletTransaction: transactionID, RefundedAtMS: nowMS, @@ -649,12 +718,12 @@ func (r *Repository) expireOneRedPacket(ctx context.Context, packetID string, no func (r *Repository) getRedPacketConfig(ctx context.Context, querier redPacketQuerier) (ledger.RedPacketConfig, error) { row := querier.QueryRowContext(ctx, ` SELECT app_code, enabled, delayed_open_seconds, expire_seconds, daily_send_limit, - updated_by_admin_id, created_at_ms, updated_at_ms + updated_by_admin_id, COALESCE(rule_url, ''), created_at_ms, updated_at_ms FROM red_packet_configs WHERE app_code = ?`, appcode.FromContext(ctx), ) - config := ledger.RedPacketConfig{AppCode: appcode.FromContext(ctx), ExpireSeconds: 300} + config := ledger.RedPacketConfig{AppCode: appcode.FromContext(ctx), ExpireSeconds: ledger.RedPacketExpireSeconds} if err := row.Scan( &config.AppCode, &config.Enabled, @@ -662,6 +731,7 @@ func (r *Repository) getRedPacketConfig(ctx context.Context, querier redPacketQu &config.ExpireSeconds, &config.DailySendLimit, &config.UpdatedByAdminID, + &config.RuleURL, &config.CreatedAtMS, &config.UpdatedAtMS, ); err != nil { @@ -670,6 +740,7 @@ func (r *Repository) getRedPacketConfig(ctx context.Context, querier redPacketQu } return config, nil } + config.ExpireSeconds = ledger.RedPacketExpireSeconds counts, err := r.listRedPacketCountTiers(ctx, querier) if err != nil { return ledger.RedPacketConfig{}, err @@ -869,19 +940,32 @@ func scanRedPacket(row redPacketScanner) (ledger.RedPacket, error) { return packet, err } -func (r *Repository) userClaimedRedPacket(ctx context.Context, tx *sql.Tx, packetID string, userID int64) (bool, error) { - var claimID string +func (r *Repository) getUserRedPacketClaim(ctx context.Context, tx *sql.Tx, packetID string, userID int64) (ledger.RedPacketClaim, bool, error) { + var claim ledger.RedPacketClaim err := tx.QueryRowContext(ctx, ` - SELECT claim_id + SELECT app_code, claim_id, command_id, packet_id, user_id, amount, wallet_transaction_id, + status, failure_reason, created_at_ms, updated_at_ms FROM red_packet_claims WHERE app_code = ? AND packet_id = ? AND user_id = ? LIMIT 1`, appcode.FromContext(ctx), packetID, userID, - ).Scan(&claimID) + ).Scan( + &claim.AppCode, + &claim.ClaimID, + &claim.CommandID, + &claim.PacketID, + &claim.UserID, + &claim.Amount, + &claim.WalletTransactionID, + &claim.Status, + &claim.FailureReason, + &claim.CreatedAtMS, + &claim.UpdatedAtMS, + ) if errors.Is(err, sql.ErrNoRows) { - return false, nil + return ledger.RedPacketClaim{}, false, nil } - return err == nil, err + return claim, err == nil, err } func (r *Repository) lockOneRedPacketAllocation(ctx context.Context, tx *sql.Tx, packetID string) (int32, int64, error) { @@ -944,6 +1028,50 @@ func (r *Repository) attachViewerClaims(ctx context.Context, packets []ledger.Re return rows.Err() } +func (r *Repository) attachRefunds(ctx context.Context, packets []ledger.RedPacket) error { + if len(packets) == 0 { + return nil + } + packetIDs := make([]string, 0, len(packets)) + indexByID := make(map[string]int, len(packets)) + for index := range packets { + packetIDs = append(packetIDs, packets[index].PacketID) + indexByID[packets[index].PacketID] = index + } + placeholders := strings.TrimRight(strings.Repeat("?,", len(packetIDs)), ",") + args := []any{appcode.FromContext(ctx)} + for _, packetID := range packetIDs { + args = append(args, packetID) + } + rows, err := r.db.QueryContext(ctx, ` + SELECT packet_id, refund_amount, status, updated_at_ms + FROM red_packet_refunds + WHERE app_code = ? AND packet_id IN (`+placeholders+`)`, + args..., + ) + if err != nil { + return err + } + defer rows.Close() + for rows.Next() { + var packetID string + var amount int64 + var status string + var updatedAtMS int64 + if err := rows.Scan(&packetID, &amount, &status, &updatedAtMS); err != nil { + return err + } + if index, ok := indexByID[packetID]; ok { + packets[index].RefundAmount = amount + packets[index].RefundStatus = status + if status == ledger.RedPacketStatusRefunded { + packets[index].RefundedAtMS = updatedAtMS + } + } + } + return rows.Err() +} + func (r *Repository) listRedPacketClaims(ctx context.Context, packetID string) ([]ledger.RedPacketClaim, error) { rows, err := r.db.QueryContext(ctx, ` SELECT app_code, claim_id, command_id, packet_id, user_id, amount, wallet_transaction_id, @@ -1015,7 +1143,11 @@ func (r *Repository) redPacketClaimReceiptForTransaction(ctx context.Context, tx if err := json.Unmarshal([]byte(raw), &metadata); err != nil { return ledger.RedPacketClaimReceipt{}, err } - return ledger.RedPacketClaimReceipt{Claim: redPacketClaimFromMetadata(metadata), BalanceAfter: metadata.BalanceAfter}, nil + packet, err := r.getRedPacket(ctx, tx, metadata.PacketID) + if err != nil { + return ledger.RedPacketClaimReceipt{Claim: redPacketClaimFromMetadata(metadata), BalanceAfter: metadata.BalanceAfter}, nil + } + return ledger.RedPacketClaimReceipt{Claim: redPacketClaimFromMetadata(metadata), Packet: packet, BalanceAfter: metadata.BalanceAfter}, nil } func redPacketFromMetadata(metadata redPacketMetadata) ledger.RedPacket { diff --git a/services/wallet-service/internal/transport/grpc/red_packet.go b/services/wallet-service/internal/transport/grpc/red_packet.go index 00486b86..a94e382a 100644 --- a/services/wallet-service/internal/transport/grpc/red_packet.go +++ b/services/wallet-service/internal/transport/grpc/red_packet.go @@ -33,6 +33,7 @@ func (s *Server) UpdateRedPacketConfig(ctx context.Context, req *walletv1.Update ExpireSeconds: req.GetExpireSeconds(), DailySendLimit: req.GetDailySendLimit(), UpdatedByAdminID: req.GetOperatorUserId(), + RuleURL: req.GetRuleUrl(), }) if err != nil { return nil, xerr.ToGRPCError(err) @@ -80,6 +81,7 @@ func (s *Server) ClaimRedPacket(ctx context.Context, req *walletv1.ClaimRedPacke Claim: redPacketClaimToProto(receipt.Claim), BalanceAfter: receipt.BalanceAfter, ServerTimeMs: time.Now().UTC().UnixMilli(), + Packet: redPacketToProto(receipt.Packet), }, nil } @@ -130,6 +132,19 @@ func (s *Server) ExpireRedPackets(ctx context.Context, req *walletv1.ExpireRedPa }, nil } +func (s *Server) RetryRedPacketRefund(ctx context.Context, req *walletv1.RetryRedPacketRefundRequest) (*walletv1.RetryRedPacketRefundResponse, error) { + ctx = appcode.WithContext(ctx, req.GetAppCode()) + receipt, err := s.svc.RetryRedPacketRefund(ctx, req.GetAppCode(), req.GetPacketId(), req.GetOperatorUserId()) + if err != nil { + return nil, xerr.ToGRPCError(err) + } + return &walletv1.RetryRedPacketRefundResponse{ + Packet: redPacketToProto(receipt.Packet), + RefundedAmount: receipt.RefundedAmount, + ServerTimeMs: time.Now().UTC().UnixMilli(), + }, nil +} + func redPacketConfigToProto(config ledger.RedPacketConfig) *walletv1.RedPacketConfig { return &walletv1.RedPacketConfig{ AppCode: config.AppCode, @@ -142,6 +157,7 @@ func redPacketConfigToProto(config ledger.RedPacketConfig) *walletv1.RedPacketCo UpdatedByAdminId: config.UpdatedByAdminID, CreatedAtMs: config.CreatedAtMS, UpdatedAtMs: config.UpdatedAtMS, + RuleUrl: config.RuleURL, } } @@ -170,6 +186,9 @@ func redPacketToProto(packet ledger.RedPacket) *walletv1.RedPacket { ClaimedByViewer: packet.ClaimedByViewer, ViewerClaimAmount: packet.ViewerClaimAmount, Claims: claims, + RefundAmount: packet.RefundAmount, + RefundStatus: packet.RefundStatus, + RefundedAtMs: packet.RefundedAtMS, } }