feat: add voice room red packet APIs

This commit is contained in:
zhx 2026-05-29 17:36:33 +08:00
parent 7433b24411
commit 880f151feb
20 changed files with 1443 additions and 191 deletions

View File

@ -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,
},

View File

@ -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);
}

View File

@ -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",

View File

@ -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)
}

View File

@ -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 == "" {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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() {

View File

@ -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,

View File

@ -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,
}
}

View File

@ -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(),
}
}

View File

@ -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',

View File

@ -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

View File

@ -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))

View File

@ -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)

View File

@ -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 {

View File

@ -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,
}
}