diff --git a/cmd/openim-rpc/openim-rpc-redpacket/client-integration-guide.md b/cmd/openim-rpc/openim-rpc-redpacket/client-integration-guide.md index aaf3b50d0..19ad75d16 100644 --- a/cmd/openim-rpc/openim-rpc-redpacket/client-integration-guide.md +++ b/cmd/openim-rpc/openim-rpc-redpacket/client-integration-guide.md @@ -1,8 +1,9 @@ # RedPacket 前端对接文档 -本文档面向前端 / 网关 / App 对接方,说明红包领取和钱包绑定的真实接入方式,重点覆盖: +本文档面向前端 / 网关 / App 对接方,说明红包创建、领取和钱包绑定的真实接入方式,重点覆盖: - 如何把当前登录用户传递给红包服务 +- 如何创建红包(业务单 + 链上创建 + 回写激活) - 如何绑定钱包 - 如何申请领取签名 - 前端何时发链、何时回写后端 @@ -97,7 +98,111 @@ Content-Type: application/json 已经在后端建立了有效绑定关系。 -## 3. 领取签名流程 +## 3. 创建红包流程 + +### 4.1 流程图 + +```text +前端 -> 红包服务: POST /api/redpacket/create-order +红包服务 -> 前端: biz_id (状态 PENDING) +前端 -> 钱包/链上: createFixedPacket/createRandomPacket/createTransfer +链上 -> 前端: tx_hash + packet_id(从事件或回执解析) +前端 -> 红包服务: POST /api/redpacket/created-callback +红包服务 -> 红包服务: 校验创建参数并激活红包 +红包服务 -> 前端: 回写成功 (状态 ACTIVE) +前端 -> 红包服务: POST /api/redpacket/detail (可选) +``` + +### 3.2 创建业务单(发链前必调) + +请求: + +```http +POST /api/redpacket/create-order +token: +Content-Type: application/json +``` + +```json +{ + "chain_type": "EVM", + "chain_id": 1, + "contract_address": "0xA1f42567559aBA5Ff0aac84cdE1AaF1F9DbB888F", + "creator_wallet": "0x1111111111111111111111111111111111111111", + "group_id": "g001", + "scope_type": "GROUP", + "receiver_user_id": "", + "receiver_user_ids": [], + "packet_type": 1, + "token": "0x2222222222222222222222222222222222222222", + "total_amount": "1000000000000000000", + "total_shares": 10, + "expiry_at": 0, + "remark": "happy new year" +} +``` + +关键说明: + +- 不需要传 `user_id`,创建人从上下文 `opUserID` 取 +- `total_amount` 必须是链上最小单位十进制字符串(例如 wei) +- `packet_type`: `0` 固定红包,`1` 拼手气红包,`2` 转账 +- `scope_type=GROUP` 时必须传 `group_id` +- `scope_type=DIRECT` 时必须传 `receiver_user_id` 或 `receiver_user_ids` + +成功响应里最关键的是: + +- `biz_id`: 业务红包单号(后续回写必须带上) + +### 3.3 链上创建红包 + +前端拿到 `biz_id` 后,再调用链上创建方法: + +- 固定红包:`createFixedPacket(...)` +- 拼手气红包:`createRandomPacket(...)` +- 转账红包:`createTransfer(...)` + +链上交易成功后,前端需要得到: + +- `tx_hash` +- `packet_id`(优先从 `PacketCreated` 事件解析) + +### 3.4 创建回写(激活红包) + +请求: + +```http +POST /api/redpacket/created-callback +token: +Content-Type: application/json +``` + +```json +{ + "biz_id": "f8a0f87e-d9cb-4d4a-8350-7bd43ab2e9a4", + "tx_hash": "0xabc123...", + "packet_id": "10001", + "group_id": "g001", + "scope_type": "GROUP", + "receiver_user_id": "", + "receiver_user_ids": [] +} +``` + +说明: + +- `biz_id`、`tx_hash` 必填 +- 推荐传 `packet_id`(可减少后端 fallback 分支) +- 回写成功后红包状态从 `PENDING` 变为 `ACTIVE` +- 回写后可调 `/api/redpacket/detail` 刷新页面状态 + +### 3.5 创建流程常见坑 + +- 先发链再 `create_order`:会导致回写阶段缺少有效 `biz_id` +- `create_order` 的 `creator_wallet` 与实际发链钱包不一致:可能被后端校验拦截 +- 未调用 `created_callback`:红包会一直停留在 `PENDING`,领取侧会失败 + +## 4. 领取签名流程 ### 3.1 流程图 @@ -111,7 +216,7 @@ Content-Type: application/json 链监听器 -> 红包服务: 最终确认领取结果 ``` -### 3.2 申请领取签名 +### 4.2 申请领取签名 请求: @@ -159,7 +264,7 @@ Content-Type: application/json } ``` -### 3.3 前端拿到响应后要做什么 +### 4.3 前端拿到响应后要做什么 前端必须原样把这些参数传给链上: @@ -182,7 +287,7 @@ claim(packetId, authNonce, randomSeed, deadline, signature) - 不要对摘要再次做 `signMessage` - 后端返回的 `signature` 已经是最终可上链签名 -## 4. 领取结果回写 +## 5. 领取结果回写 `claim-result` 是可选的,主要作用是让业务侧尽快看到一条 `PENDING` 领取记录。 @@ -210,29 +315,38 @@ Content-Type: application/json - 如果不能,会先记成 `PENDING` - 最终仍以链监听器为准 -## 5. 前端推荐调用顺序 +## 6. 前端推荐调用顺序 + +### 6.1 创建红包 + +1. 用户登录业务系统 +2. 前端请求 `/api/redpacket/create-order` +3. 拿到 `biz_id` 后,钱包调用链上创建红包方法 +4. 从交易回执/事件拿到 `tx_hash`、`packet_id` +5. 前端请求 `/api/redpacket/created-callback` +6. 前端请求 `/api/redpacket/detail` 刷新状态(确认 `ACTIVE`) -### 5.1 首次使用钱包领取 +### 6.2 首次使用钱包领取 1. 用户登录业务系统 -2. 前端请求 `/wallet-bind/challenge` +2. 前端请求 `/api/redpacket/wallet-bind/challenge` 3. 钱包对 `message` 签名 -4. 前端请求 `/wallet-bind/confirm` +4. 前端请求 `/api/redpacket/wallet-bind/confirm` 5. 绑定成功后再进入领取流程 -### 5.2 正常领取 +### 6.3 正常领取 1. 前端拿到红包 `packet_id` 2. 用户连接钱包,得到本次 `claimer` 地址 -3. 前端请求 `/claim-sign` +3. 前端请求 `/api/redpacket/claim-sign` 4. 拿到 `auth_nonce + random_seed + deadline + signature` 5. 前端调用链上 `claim(...)` -6. 前端可选请求 `/claim-result` +6. 前端可选请求 `/api/redpacket/claim-result` 7. 页面轮询详情页或等待业务侧状态同步 -## 6. 常见错误和排查 +## 7. 常见错误和排查 -### 6.1 `op user id missing in context` +### 7.1 `op user id missing in context` 原因: @@ -240,7 +354,7 @@ Content-Type: application/json - 网关没有把 `opUserID` 注入上下文 - 直接绕过网关调用了红包服务 -### 6.2 `wallet is not bound to user` +### 7.2 `wallet is not bound to user` 原因: @@ -248,20 +362,20 @@ Content-Type: application/json - 当前钱包绑定的是别的业务用户 - 链类型不一致 -### 6.3 `already claimed` +### 7.3 `already claimed` 原因: - 同一个钱包地址已经领过该红包 -### 6.4 `user already claimed` +### 7.4 `user already claimed` 原因: - 同一个业务用户已经领取过该红包 - 即使换钱包地址,也会被后端拦截 -## 7. 后端接口与代码位置 +## 8. 后端接口与代码位置 - 接口契约文档: [backend-api.md](/Users/panda/aiCode/red_packet/open-im-server-origin/cmd/openim-rpc/openim-rpc-redpacket/backend-api.md) diff --git a/config/openim-rpc-redpacket.yml b/config/openim-rpc-redpacket.yml index 0af851ba2..7ae1aaa9e 100644 --- a/config/openim-rpc-redpacket.yml +++ b/config/openim-rpc-redpacket.yml @@ -12,11 +12,11 @@ prometheus: # Leave rpcURL empty to disable the EVM client; the RPC service will then # only expose TRON-related functionality (or the offchain code paths). chain: - rpcURL: "" - contractAddress: "" - chainID: 0 - signerPrivateKey: "" - configAdminPrivateKey: "" + rpcURL: "https://data-seed-prebsc-1-s1.bnbchain.org:8545" + contractAddress: "0x9f2e22F5D0cf8d8127E319D38b3EDDaE43bb4DC0" + chainID: 97 + signerPrivateKey: "e9f6a5f3a3c3a97099ca31e7f44151e529c0a4f8a91d5d4232c7282f2b798df4" + configAdminPrivateKey: "e9f6a5f3a3c3a97099ca31e7f44151e529c0a4f8a91d5d4232c7282f2b798df4" # TRON full-node configuration. Leave fullNodeURL empty to disable TRON. tron: @@ -27,7 +27,8 @@ tron: feeLimit: 100000000 # Indexer polling interval (in seconds). Used by both EVM and TRON event indexers. +# Set to 0 or negative to disable block scanning completely and rely on tx-hash parsing paths. indexer: - pollInterval: 5 + pollInterval: 0 # EVM only: max block span per eth_getLogs request (0 = default 2000). Increase if your node allows larger ranges. maxBlocksPerPoll: 2000 diff --git a/internal/rpc/redpacket/admin.go b/internal/rpc/redpacket/admin.go index e2802d7cb..ec4890bf8 100644 --- a/internal/rpc/redpacket/admin.go +++ b/internal/rpc/redpacket/admin.go @@ -173,7 +173,7 @@ func (s *redPacketServer) ParseTxEvents(ctx context.Context, req *pbredpacket.Pa if s.tronClient == nil { return nil, errs.ErrInternalServer.WrapMsg("TRON client not configured") } - events, err := s.tronClient.ParseTransactionReceipt(ctx, req.TxHash) + success, events, err := s.tronClient.ParseTransactionReceiptWithStatus(ctx, req.TxHash) if err != nil { return nil, errs.ErrInternalServer.WrapMsg("parse TRON tx receipt failed: " + err.Error()) } @@ -185,12 +185,16 @@ func (s *redPacketServer) ParseTxEvents(ctx context.Context, req *pbredpacket.Pa } out = append(out, &pbredpacket.ParsedEvent{Name: e.Name, Data: data}) } - return &pbredpacket.ParseTxEventsResp{Chain: "tron", TxHash: req.TxHash, Events: out}, nil + note := "tx_status=FAILED" + if success { + note = "tx_status=SUCCESS" + } + return &pbredpacket.ParseTxEventsResp{Chain: "tron", TxHash: req.TxHash, Events: out, Note: note}, nil } if s.chainClient != nil { txHashBytes := common.HexToHash(req.TxHash) - events, err := s.chainClient.ParseTransactionReceipt(ctx, txHashBytes) + success, events, err := s.chainClient.ParseTransactionReceiptWithStatus(ctx, txHashBytes) if err != nil { return nil, errs.ErrInternalServer.WrapMsg("parse tx receipt failed: " + err.Error()) } @@ -206,10 +210,15 @@ func (s *redPacketServer) ParseTxEvents(ctx context.Context, req *pbredpacket.Pa Data: data, }) } + note := "tx_status=FAILED" + if success { + note = "tx_status=SUCCESS" + } return &pbredpacket.ParseTxEventsResp{ Chain: "eth", TxHash: req.TxHash, Events: out, + Note: note, }, nil } diff --git a/internal/rpc/redpacket/chain/client.go b/internal/rpc/redpacket/chain/client.go index 896e8c903..0db953f56 100644 --- a/internal/rpc/redpacket/chain/client.go +++ b/internal/rpc/redpacket/chain/client.go @@ -113,12 +113,29 @@ func (c *ChainClient) SignClaim(digest [32]byte) ([]byte, error) { } func (c *ChainClient) ParseTransactionReceipt(ctx context.Context, txHash common.Hash) ([]*ParsedEvent, error) { + _, events, err := c.ParseTransactionReceiptWithStatus(ctx, txHash) + return events, err +} + +// ParseTransactionReceiptWithStatus fetches tx receipt once and returns both +// execution status and decoded contract events. +func (c *ChainClient) ParseTransactionReceiptWithStatus(ctx context.Context, txHash common.Hash) (bool, []*ParsedEvent, error) { receipt, err := c.client.TransactionReceipt(ctx, txHash) if err != nil { - return nil, fmt.Errorf("get receipt failed: %w", err) + return false, nil, fmt.Errorf("get receipt failed: %w", err) } + events, err := ParseEventsFromLogs(receipt.Logs, c.contractABI) + if err != nil { + return false, nil, err + } + return receipt.Status == types.ReceiptStatusSuccessful, events, nil +} - return ParseEventsFromLogs(receipt.Logs, c.contractABI) +// IsTransactionSuccessful reports whether the EVM transaction executed +// successfully according to receipt.status (1=success, 0=failure). +func (c *ChainClient) IsTransactionSuccessful(ctx context.Context, txHash common.Hash) (bool, error) { + success, _, err := c.ParseTransactionReceiptWithStatus(ctx, txHash) + return success, err } func (c *ChainClient) ContractAddress() common.Address { diff --git a/internal/rpc/redpacket/chain/tron.go b/internal/rpc/redpacket/chain/tron.go index 08ff077da..0792fb64e 100644 --- a/internal/rpc/redpacket/chain/tron.go +++ b/internal/rpc/redpacket/chain/tron.go @@ -63,17 +63,28 @@ func (t *TronClient) FullNodeURL() string { } func (t *TronClient) ParseTransactionReceipt(ctx context.Context, txID string) ([]*ParsedEvent, error) { + _, events, err := t.ParseTransactionReceiptWithStatus(ctx, txID) + return events, err +} + +// ParseTransactionReceiptWithStatus fetches tx info once and returns both +// execution status and decoded contract events. +func (t *TronClient) ParseTransactionReceiptWithStatus(ctx context.Context, txID string) (bool, []*ParsedEvent, error) { info, err := t.getTransactionInfo(ctx, txID) if err != nil { - return nil, err + return false, nil, err } logs, err := tronLogsToEVMLogs(info, txID) if err != nil { - return nil, err + return false, nil, err } - - return ParseEventsFromLogs(logs, t.parsedABI) + events, err := ParseEventsFromLogs(logs, t.parsedABI) + if err != nil { + return false, nil, err + } + success := strings.EqualFold(info.Receipt.Result, "SUCCESS") + return success, events, nil } func (t *TronClient) SendAdminTransaction(ctx context.Context, methodName string, args ...interface{}) (string, error) { @@ -111,13 +122,23 @@ func (t *TronClient) GetSignMessageForTron(ctx context.Context, packetID *big.In type tronTxInfoResp struct { ID string `json:"id"` BlockNumber uint64 `json:"blockNumber"` - Log []struct { + Receipt struct { + Result string `json:"result"` + } `json:"receipt"` + Log []struct { Address string `json:"address"` Topics []string `json:"topics"` Data string `json:"data"` } `json:"log"` } +// IsTransactionSuccessful reports whether the TRON transaction execution +// succeeded based on receipt.result == "SUCCESS". +func (t *TronClient) IsTransactionSuccessful(ctx context.Context, txID string) (bool, error) { + success, _, err := t.ParseTransactionReceiptWithStatus(ctx, txID) + return success, err +} + func getParamTypes(args []interface{}) string { types := make([]string, len(args)) for i, arg := range args { diff --git a/internal/rpc/redpacket/redpacket.go b/internal/rpc/redpacket/redpacket.go index 4609fc488..06e67ed16 100644 --- a/internal/rpc/redpacket/redpacket.go +++ b/internal/rpc/redpacket/redpacket.go @@ -137,13 +137,17 @@ func Start(ctx context.Context, conf *Config, registry discovery.SvcDiscoveryReg pbredpacket.RegisterRedPacketServer(server, srv) - if chainClient != nil { - ethIndexer := chain.NewIndexer(chainClient, repo, conf.RpcConfig.Indexer.PollInterval, 0, conf.RpcConfig.Indexer.MaxBlocksPerPoll) - ethIndexer.Start(ctx) - } - if tronClient != nil { - tronIndexer := chain.NewTronIndexer(tronClient, repo, conf.RpcConfig.Indexer.PollInterval, 0) - tronIndexer.Start(ctx) + if conf.RpcConfig.Indexer.PollInterval > 0 { + if chainClient != nil { + ethIndexer := chain.NewIndexer(chainClient, repo, conf.RpcConfig.Indexer.PollInterval, 0) + ethIndexer.Start(ctx) + } + if tronClient != nil { + tronIndexer := chain.NewTronIndexer(tronClient, repo, conf.RpcConfig.Indexer.PollInterval, 0) + tronIndexer.Start(ctx) + } + } else { + log.ZInfo(ctx, "redpacket indexer disabled by config", "pollInterval", conf.RpcConfig.Indexer.PollInterval) } return nil diff --git a/internal/rpc/redpacket/service.go b/internal/rpc/redpacket/service.go index 71aad905a..185a1bb40 100644 --- a/internal/rpc/redpacket/service.go +++ b/internal/rpc/redpacket/service.go @@ -124,6 +124,12 @@ func (s *redPacketServer) CreatedCallback(ctx context.Context, req *pbredpacket. PacketID: createdPacket.PacketID, ChainID: createdPacket.ChainID, ContractAddress: createdPacket.ContractAddress, + CreatorWallet: createdPacket.CreatorWallet, + PacketType: createdPacket.PacketType, + Token: createdPacket.Token, + TotalAmount: createdPacket.TotalAmount, + TotalShares: createdPacket.TotalShares, + ExpiryAt: createdPacket.ExpiryAt, TxHash: req.TxHash, GroupID: groupID, ScopeType: scopeType, @@ -268,15 +274,36 @@ func (s *redPacketServer) ClaimResult(ctx context.Context, req *pbredpacket.Clai return nil, err } - claimedEvent, err := s.resolveClaimedEvent(ctx, rp, req.TxHash) + txSuccess, events, err := s.parseChainReceiptWithStatus(ctx, rp, req.TxHash) + if err != nil { + log.ZWarn(ctx, "parse claim receipt failed", err, "txHash", req.TxHash) + return &pbredpacket.ClaimResultResp{}, nil + } + if !txSuccess { + if markErr := s.markClaimFailed(ctx, req.PacketID, currentUserID, req.Claimer, req.TxHash); markErr != nil { + log.ZWarn(ctx, "mark claim failed status failed", markErr, "txHash", req.TxHash) + } + return &pbredpacket.ClaimResultResp{}, nil + } + + claimedEvent, err := resolveClaimedEventFromParsedEvents(rp, events) if err != nil { log.ZWarn(ctx, "resolve claim event failed", err, "txHash", req.TxHash) + if markErr := s.markClaimFailed(ctx, req.PacketID, currentUserID, req.Claimer, req.TxHash); markErr != nil { + log.ZWarn(ctx, "mark claim failed status failed", markErr, "txHash", req.TxHash) + } return &pbredpacket.ClaimResultResp{}, nil } if claimedEvent == nil { + if markErr := s.markClaimFailed(ctx, req.PacketID, currentUserID, req.Claimer, req.TxHash); markErr != nil { + log.ZWarn(ctx, "mark claim failed status failed", markErr, "txHash", req.TxHash) + } return &pbredpacket.ClaimResultResp{}, nil } if !strings.EqualFold(claimedEvent.ClaimerWallet, req.Claimer) { + if markErr := s.markClaimFailed(ctx, req.PacketID, currentUserID, req.Claimer, req.TxHash); markErr != nil { + log.ZWarn(ctx, "mark claim failed status failed", markErr, "txHash", req.TxHash) + } return nil, errs.ErrArgs.WrapMsg(fmt.Sprintf("claim event claimer mismatch: got %s want %s", claimedEvent.ClaimerWallet, req.Claimer)) } @@ -311,6 +338,34 @@ func (s *redPacketServer) ClaimResult(ctx context.Context, req *pbredpacket.Clai return &pbredpacket.ClaimResultResp{}, nil } +func (s *redPacketServer) parseChainReceiptWithStatus(ctx context.Context, rp *model.RedPacket, txHash string) (bool, []*chain.ParsedEvent, error) { + switch rp.ChainType { + case "EVM": + if s.chainClient == nil { + return false, nil, errs.ErrInternalServer.WrapMsg("evm client is unavailable") + } + return s.chainClient.ParseTransactionReceiptWithStatus(ctx, common.HexToHash(txHash)) + case "TRON": + if s.tronClient == nil { + return false, nil, errs.ErrInternalServer.WrapMsg("tron client is unavailable") + } + return s.tronClient.ParseTransactionReceiptWithStatus(ctx, txHash) + default: + return false, nil, errs.ErrArgs.WrapMsg("unsupported chain_type: " + rp.ChainType) + } +} + +func (s *redPacketServer) markClaimFailed(ctx context.Context, packetID, userID, claimer, txHash string) error { + return s.db.SaveClaim(ctx, &model.RedPacketClaim{ + PacketID: packetID, + UserID: userID, + ClaimerWallet: claimer, + ClaimTxHash: txHash, + Status: "FAILED", + UpdatedAt: time.Now(), + }) +} + // canClaim runs the claim-eligibility check (formerly RedPacketService.CanClaim). func (s *redPacketServer) canClaim(ctx context.Context, packetID, claimer, userID string) error { rp, err := s.db.GetRedPacketByPacketID(ctx, packetID) @@ -344,6 +399,12 @@ type claimedEventSnapshot struct { BlockNumber uint64 } +type refundedEventSnapshot struct { + RefundTo string + Amount string + BlockNumber uint64 +} + type createdPacketSnapshot struct { PacketID string ChainID int64 @@ -367,10 +428,13 @@ func (s *redPacketServer) resolveCreatedPacket(ctx context.Context, rp *model.Re return buildFallbackCreatedPacket(rp, fallbackPacketID), nil } - events, err := s.chainClient.ParseTransactionReceipt(ctx, common.HexToHash(txHashHex)) + success, events, err := s.chainClient.ParseTransactionReceiptWithStatus(ctx, common.HexToHash(txHashHex)) if err != nil { return nil, errs.ErrInternalServer.WrapMsg("parse created tx failed: " + err.Error()) } + if !success { + return nil, errs.ErrArgs.WrapMsg("created tx execution failed on chain") + } for _, event := range events { if event.Name != "PacketCreated" { @@ -396,10 +460,13 @@ func (s *redPacketServer) resolveCreatedPacket(ctx context.Context, rp *model.Re return buildFallbackCreatedPacket(rp, fallbackPacketID), nil } - events, err := s.tronClient.ParseTransactionReceipt(ctx, txHashHex) + success, events, err := s.tronClient.ParseTransactionReceiptWithStatus(ctx, txHashHex) if err != nil { return nil, errs.ErrInternalServer.WrapMsg("parse tron created tx failed: " + err.Error()) } + if !success { + return nil, errs.ErrArgs.WrapMsg("created tx execution failed on chain") + } for _, event := range events { if event.Name != "PacketCreated" { @@ -795,7 +862,10 @@ func (s *redPacketServer) resolveClaimedEvent(ctx context.Context, rp *model.Red if err != nil { return nil, err } + return resolveClaimedEventFromParsedEvents(rp, events) +} +func resolveClaimedEventFromParsedEvents(rp *model.RedPacket, events []*chain.ParsedEvent) (*claimedEventSnapshot, error) { for _, event := range events { if event.Name != "PacketClaimed" { continue @@ -816,6 +886,24 @@ func (s *redPacketServer) resolveClaimedEvent(ctx context.Context, rp *model.Red return nil, nil } +func resolveRefundedEventFromParsedEvents(rp *model.RedPacket, events []*chain.ParsedEvent) (*refundedEventSnapshot, error) { + for _, event := range events { + if event.Name != "PacketRefunded" { + continue + } + packetID := chain.GetPacketIDFromEvent(event).String() + if packetID != rp.PacketID { + return nil, errs.ErrArgs.WrapMsg(fmt.Sprintf("refund event packet mismatch: got %s want %s", packetID, rp.PacketID)) + } + return &refundedEventSnapshot{ + RefundTo: strings.ToLower(chain.GetAddressFromEvent(event, "refundTo").Hex()), + Amount: chain.GetAmountFromEvent(event).String(), + BlockNumber: event.BlockNumber, + }, nil + } + return nil, nil +} + // maxTotalShares caps the number of shares to prevent abuse. const maxTotalShares = 10_000 @@ -946,7 +1034,37 @@ func (s *redPacketServer) RequestRefund(ctx context.Context, req *pbredpacket.Re } log.ZInfo(ctx, "redpacket refund submitted", "packetID", rp.PacketID, "txHash", txHash) - return &pbredpacket.RequestRefundResp{TxHash: txHash, Status: "PENDING"}, nil + txSuccess, events, parseErr := s.parseChainReceiptWithStatus(ctx, rp, txHash) + if parseErr != nil { + log.ZWarn(ctx, "parse refund receipt failed, fallback to async indexer", parseErr, "packetID", rp.PacketID, "txHash", txHash) + return &pbredpacket.RequestRefundResp{TxHash: txHash, Status: "PENDING"}, nil + } + if !txSuccess { + return &pbredpacket.RequestRefundResp{TxHash: txHash, Status: "FAILED"}, nil + } + + refundedEvent, err := resolveRefundedEventFromParsedEvents(rp, events) + if err != nil { + log.ZWarn(ctx, "resolve refunded event failed, fallback to async indexer", err, "packetID", rp.PacketID, "txHash", txHash) + return &pbredpacket.RequestRefundResp{TxHash: txHash, Status: "PENDING"}, nil + } + if refundedEvent == nil { + return &pbredpacket.RequestRefundResp{TxHash: txHash, Status: "PENDING"}, nil + } + + if err := s.db.SaveRefund(ctx, &model.RedPacketRefund{ + PacketID: rp.PacketID, + RefundTo: refundedEvent.RefundTo, + TxHash: txHash, + Amount: refundedEvent.Amount, + CreatedAt: time.Now(), + }); err != nil { + return nil, err + } + if err := s.db.UpdateRedPacketStatus(ctx, rp.PacketID, "REFUNDED"); err != nil { + return nil, err + } + return &pbredpacket.RequestRefundResp{TxHash: txHash, Status: "REFUNDED"}, nil } func (s *redPacketServer) GetRefund(ctx context.Context, req *pbredpacket.GetRefundReq) (*pbredpacket.GetRefundResp, error) { diff --git a/pkg/common/storage/database/mgo/redpacket.go b/pkg/common/storage/database/mgo/redpacket.go index 0cf51b4c5..536551439 100644 --- a/pkg/common/storage/database/mgo/redpacket.go +++ b/pkg/common/storage/database/mgo/redpacket.go @@ -75,6 +75,12 @@ func (m *RedPacketMgo) UpdateCreated(ctx context.Context, rp *model.RedPacket) e "tx_hash": rp.TxHash, "chain_id": rp.ChainID, "contract_address": rp.ContractAddress, + "creator_wallet": rp.CreatorWallet, + "packet_type": rp.PacketType, + "token": rp.Token, + "total_amount": rp.TotalAmount, + "total_shares": rp.TotalShares, + "expiry_at": rp.ExpiryAt, "group_id": rp.GroupID, "scope_type": rp.ScopeType, "receiver_user_id": rp.ReceiverUserID,