From db1251c7619f8cbfa73ae8508ba1720f5899bda6 Mon Sep 17 00:00:00 2001 From: JiaJun <2394389886@qq.com> Date: Mon, 6 Jul 2026 16:52:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E5=85=85=E5=80=BC?= =?UTF-8?q?=E6=8F=90=E7=8E=B0=E9=85=8D=E7=BD=AE=E5=92=8C=E5=85=85=E5=80=BC?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agents/skills/gitnexus/gitnexus-cli/SKILL.md | 83 ++++++++++++ .../gitnexus/gitnexus-debugging/SKILL.md | 89 +++++++++++++ .../gitnexus/gitnexus-exploring/SKILL.md | 78 +++++++++++ .../skills/gitnexus/gitnexus-guide/SKILL.md | 64 +++++++++ .../gitnexus-impact-analysis/SKILL.md | 97 ++++++++++++++ .../gitnexus/gitnexus-refactoring/SKILL.md | 121 ++++++++++++++++++ pnpm-workspace.yaml | 4 + src/api/finance-api.ts | 62 ++++++++- src/constants/game.ts | 28 ++++ .../game/components/desktop/desktop-topup.tsx | 49 ++++++- .../game/components/mobile/mobile-topup.tsx | 49 ++++++- src/hooks/use-withdraw-vm.ts | 17 ++- src/type/api.type.ts | 102 ++++++++++++++- 13 files changed, 830 insertions(+), 13 deletions(-) create mode 100644 .agents/skills/gitnexus/gitnexus-cli/SKILL.md create mode 100644 .agents/skills/gitnexus/gitnexus-debugging/SKILL.md create mode 100644 .agents/skills/gitnexus/gitnexus-exploring/SKILL.md create mode 100644 .agents/skills/gitnexus/gitnexus-guide/SKILL.md create mode 100644 .agents/skills/gitnexus/gitnexus-impact-analysis/SKILL.md create mode 100644 .agents/skills/gitnexus/gitnexus-refactoring/SKILL.md create mode 100644 pnpm-workspace.yaml diff --git a/.agents/skills/gitnexus/gitnexus-cli/SKILL.md b/.agents/skills/gitnexus/gitnexus-cli/SKILL.md new file mode 100644 index 0000000..291e22e --- /dev/null +++ b/.agents/skills/gitnexus/gitnexus-cli/SKILL.md @@ -0,0 +1,83 @@ +--- +name: gitnexus-cli +description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\"" +--- + +# GitNexus CLI Commands + +All commands work via `npx` — no global install required. + +## Commands + +### analyze — Build or refresh the index + +```bash +npx gitnexus analyze +``` + +Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates AGENTS.md / AGENTS.md context files. + +| Flag | Effect | +| -------------- | ---------------------------------------------------------------- | +| `--force` | Force full re-index even if up to date | +| `--embeddings` | Enable embedding generation for semantic search (off by default) | +| `--drop-embeddings` | Drop existing embeddings on rebuild. By default, an `analyze` without `--embeddings` preserves them. | + +**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Codex, a PostToolUse hook detects staleness after `git commit` and `git merge` and notifies the agent to run `analyze` — the hook does not run analyze itself, to avoid blocking the agent for up to 120s and risking KuzuDB corruption on timeout. + +### status — Check index freshness + +```bash +npx gitnexus status +``` + +Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed. + +### clean — Delete the index + +```bash +npx gitnexus clean +``` + +Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project. + +| Flag | Effect | +| --------- | ------------------------------------------------- | +| `--force` | Skip confirmation prompt | +| `--all` | Clean all indexed repos, not just the current one | + +### wiki — Generate documentation from the graph + +```bash +npx gitnexus wiki +``` + +Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use). + +| Flag | Effect | +| ------------------- | ----------------------------------------- | +| `--force` | Force full regeneration | +| `--model ` | LLM model (default: minimax/minimax-m2.5) | +| `--base-url ` | LLM API base URL | +| `--api-key ` | LLM API key | +| `--concurrency ` | Parallel LLM calls (default: 3) | +| `--gist` | Publish wiki as a public GitHub Gist | + +### list — Show all indexed repos + +```bash +npx gitnexus list +``` + +Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information. + +## After Indexing + +1. **Read `gitnexus://repo/{name}/context`** to verify the index loaded +2. Use the other GitNexus skills (`exploring`, `debugging`, `impact-analysis`, `refactoring`) for your task + +## Troubleshooting + +- **"Not inside a git repository"**: Run from a directory inside a git repo +- **Index is stale after re-analyzing**: Restart Codex to reload the MCP server +- **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding diff --git a/.agents/skills/gitnexus/gitnexus-debugging/SKILL.md b/.agents/skills/gitnexus/gitnexus-debugging/SKILL.md new file mode 100644 index 0000000..9510b97 --- /dev/null +++ b/.agents/skills/gitnexus/gitnexus-debugging/SKILL.md @@ -0,0 +1,89 @@ +--- +name: gitnexus-debugging +description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\"" +--- + +# Debugging with GitNexus + +## When to Use + +- "Why is this function failing?" +- "Trace where this error comes from" +- "Who calls this method?" +- "This endpoint returns 500" +- Investigating bugs, errors, or unexpected behavior + +## Workflow + +``` +1. gitnexus_query({query: ""}) → Find related execution flows +2. gitnexus_context({name: ""}) → See callers/callees/processes +3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow +4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] Understand the symptom (error message, unexpected behavior) +- [ ] gitnexus_query for error text or related code +- [ ] Identify the suspect function from returned processes +- [ ] gitnexus_context to see callers and callees +- [ ] Trace execution flow via process resource if applicable +- [ ] gitnexus_cypher for custom call chain traces if needed +- [ ] Read source files to confirm root cause +``` + +## Debugging Patterns + +| Symptom | GitNexus Approach | +| -------------------- | ---------------------------------------------------------- | +| Error message | `gitnexus_query` for error text → `context` on throw sites | +| Wrong return value | `context` on the function → trace callees for data flow | +| Intermittent failure | `context` → look for external calls, async deps | +| Performance issue | `context` → find symbols with many callers (hot paths) | +| Recent regression | `detect_changes` to see what your changes affect | + +## Tools + +**gitnexus_query** — find code related to error: + +``` +gitnexus_query({query: "payment validation error"}) +→ Processes: CheckoutFlow, ErrorHandling +→ Symbols: validatePayment, handlePaymentError, PaymentException +``` + +**gitnexus_context** — full context for a suspect: + +``` +gitnexus_context({name: "validatePayment"}) +→ Incoming calls: processCheckout, webhookHandler +→ Outgoing calls: verifyCard, fetchRates (external API!) +→ Processes: CheckoutFlow (step 3/7) +``` + +**gitnexus_cypher** — custom call chain traces: + +```cypher +MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"}) +RETURN [n IN nodes(path) | n.name] AS chain +``` + +## Example: "Payment endpoint returns 500 intermittently" + +``` +1. gitnexus_query({query: "payment error handling"}) + → Processes: CheckoutFlow, ErrorHandling + → Symbols: validatePayment, handlePaymentError + +2. gitnexus_context({name: "validatePayment"}) + → Outgoing calls: verifyCard, fetchRates (external API!) + +3. READ gitnexus://repo/my-app/process/CheckoutFlow + → Step 3: validatePayment → calls fetchRates (external) + +4. Root cause: fetchRates calls external API without proper timeout +``` diff --git a/.agents/skills/gitnexus/gitnexus-exploring/SKILL.md b/.agents/skills/gitnexus/gitnexus-exploring/SKILL.md new file mode 100644 index 0000000..927a4e4 --- /dev/null +++ b/.agents/skills/gitnexus/gitnexus-exploring/SKILL.md @@ -0,0 +1,78 @@ +--- +name: gitnexus-exploring +description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\"" +--- + +# Exploring Codebases with GitNexus + +## When to Use + +- "How does authentication work?" +- "What's the project structure?" +- "Show me the main components" +- "Where is the database logic?" +- Understanding code you haven't seen before + +## Workflow + +``` +1. READ gitnexus://repos → Discover indexed repos +2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness +3. gitnexus_query({query: ""}) → Find related execution flows +4. gitnexus_context({name: ""}) → Deep dive on specific symbol +5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow +``` + +> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] READ gitnexus://repo/{name}/context +- [ ] gitnexus_query for the concept you want to understand +- [ ] Review returned processes (execution flows) +- [ ] gitnexus_context on key symbols for callers/callees +- [ ] READ process resource for full execution traces +- [ ] Read source files for implementation details +``` + +## Resources + +| Resource | What you get | +| --------------------------------------- | ------------------------------------------------------- | +| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) | +| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) | +| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) | +| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) | + +## Tools + +**gitnexus_query** — find execution flows related to a concept: + +``` +gitnexus_query({query: "payment processing"}) +→ Processes: CheckoutFlow, RefundFlow, WebhookHandler +→ Symbols grouped by flow with file locations +``` + +**gitnexus_context** — 360-degree view of a symbol: + +``` +gitnexus_context({name: "validateUser"}) +→ Incoming calls: loginHandler, apiMiddleware +→ Outgoing calls: checkToken, getUserById +→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3) +``` + +## Example: "How does payment processing work?" + +``` +1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes +2. gitnexus_query({query: "payment processing"}) + → CheckoutFlow: processPayment → validateCard → chargeStripe + → RefundFlow: initiateRefund → calculateRefund → processRefund +3. gitnexus_context({name: "processPayment"}) + → Incoming: checkoutHandler, webhookHandler + → Outgoing: validateCard, chargeStripe, saveTransaction +4. Read src/payments/processor.ts for implementation details +``` diff --git a/.agents/skills/gitnexus/gitnexus-guide/SKILL.md b/.agents/skills/gitnexus/gitnexus-guide/SKILL.md new file mode 100644 index 0000000..937ac73 --- /dev/null +++ b/.agents/skills/gitnexus/gitnexus-guide/SKILL.md @@ -0,0 +1,64 @@ +--- +name: gitnexus-guide +description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\"" +--- + +# GitNexus Guide + +Quick reference for all GitNexus MCP tools, resources, and the knowledge graph schema. + +## Always Start Here + +For any task involving code understanding, debugging, impact analysis, or refactoring: + +1. **Read `gitnexus://repo/{name}/context`** — codebase overview + check index freshness +2. **Match your task to a skill below** and **read that skill file** +3. **Follow the skill's workflow and checklist** + +> If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first. + +## Skills + +| Task | Skill to read | +| -------------------------------------------- | ------------------- | +| Understand architecture / "How does X work?" | `gitnexus-exploring` | +| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` | +| Trace bugs / "Why is X failing?" | `gitnexus-debugging` | +| Rename / extract / split / refactor | `gitnexus-refactoring` | +| Tools, resources, schema reference | `gitnexus-guide` (this file) | +| Index, status, clean, wiki CLI commands | `gitnexus-cli` | + +## Tools Reference + +| Tool | What it gives you | +| ---------------- | ------------------------------------------------------------------------ | +| `query` | Process-grouped code intelligence — execution flows related to a concept | +| `context` | 360-degree symbol view — categorized refs, processes it participates in | +| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence | +| `detect_changes` | Git-diff impact — what do your current changes affect | +| `rename` | Multi-file coordinated rename with confidence-tagged edits | +| `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) | +| `list_repos` | Discover indexed repos | + +## Resources Reference + +Lightweight reads (~100-500 tokens) for navigation: + +| Resource | Content | +| ---------------------------------------------- | ----------------------------------------- | +| `gitnexus://repo/{name}/context` | Stats, staleness check | +| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores | +| `gitnexus://repo/{name}/cluster/{clusterName}` | Area members | +| `gitnexus://repo/{name}/processes` | All execution flows | +| `gitnexus://repo/{name}/process/{processName}` | Step-by-step trace | +| `gitnexus://repo/{name}/schema` | Graph schema for Cypher | + +## Graph Schema + +**Nodes:** File, Function, Class, Interface, Method, Community, Process +**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS + +```cypher +MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"}) +RETURN caller.name, caller.filePath +``` diff --git a/.agents/skills/gitnexus/gitnexus-impact-analysis/SKILL.md b/.agents/skills/gitnexus/gitnexus-impact-analysis/SKILL.md new file mode 100644 index 0000000..e19af28 --- /dev/null +++ b/.agents/skills/gitnexus/gitnexus-impact-analysis/SKILL.md @@ -0,0 +1,97 @@ +--- +name: gitnexus-impact-analysis +description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\"" +--- + +# Impact Analysis with GitNexus + +## When to Use + +- "Is it safe to change this function?" +- "What will break if I modify X?" +- "Show me the blast radius" +- "Who uses this code?" +- Before making non-trivial code changes +- Before committing — to understand what your changes affect + +## Workflow + +``` +1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this +2. READ gitnexus://repo/{name}/processes → Check affected execution flows +3. gitnexus_detect_changes() → Map current git changes to affected flows +4. Assess risk and report to user +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents +- [ ] Review d=1 items first (these WILL BREAK) +- [ ] Check high-confidence (>0.8) dependencies +- [ ] READ processes to check affected execution flows +- [ ] gitnexus_detect_changes() for pre-commit check +- [ ] Assess risk level and report to user +``` + +## Understanding Output + +| Depth | Risk Level | Meaning | +| ----- | ---------------- | ------------------------ | +| d=1 | **WILL BREAK** | Direct callers/importers | +| d=2 | LIKELY AFFECTED | Indirect dependencies | +| d=3 | MAY NEED TESTING | Transitive effects | + +## Risk Assessment + +| Affected | Risk | +| ------------------------------ | -------- | +| <5 symbols, few processes | LOW | +| 5-15 symbols, 2-5 processes | MEDIUM | +| >15 symbols or many processes | HIGH | +| Critical path (auth, payments) | CRITICAL | + +## Tools + +**gitnexus_impact** — the primary tool for symbol blast radius: + +``` +gitnexus_impact({ + target: "validateUser", + direction: "upstream", + minConfidence: 0.8, + maxDepth: 3 +}) + +→ d=1 (WILL BREAK): + - loginHandler (src/auth/login.ts:42) [CALLS, 100%] + - apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%] + +→ d=2 (LIKELY AFFECTED): + - authRouter (src/routes/auth.ts:22) [CALLS, 95%] +``` + +**gitnexus_detect_changes** — git-diff based impact analysis: + +``` +gitnexus_detect_changes({scope: "staged"}) + +→ Changed: 5 symbols in 3 files +→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline +→ Risk: MEDIUM +``` + +## Example: "What breaks if I change validateUser?" + +``` +1. gitnexus_impact({target: "validateUser", direction: "upstream"}) + → d=1: loginHandler, apiMiddleware (WILL BREAK) + → d=2: authRouter, sessionManager (LIKELY AFFECTED) + +2. READ gitnexus://repo/my-app/processes + → LoginFlow and TokenRefresh touch validateUser + +3. Risk: 2 direct callers, 2 processes = MEDIUM +``` diff --git a/.agents/skills/gitnexus/gitnexus-refactoring/SKILL.md b/.agents/skills/gitnexus/gitnexus-refactoring/SKILL.md new file mode 100644 index 0000000..f48cc01 --- /dev/null +++ b/.agents/skills/gitnexus/gitnexus-refactoring/SKILL.md @@ -0,0 +1,121 @@ +--- +name: gitnexus-refactoring +description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\"" +--- + +# Refactoring with GitNexus + +## When to Use + +- "Rename this function safely" +- "Extract this into a module" +- "Split this service" +- "Move this to a new file" +- Any task involving renaming, extracting, splitting, or restructuring code + +## Workflow + +``` +1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents +2. gitnexus_query({query: "X"}) → Find execution flows involving X +3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs +4. Plan update order: interfaces → implementations → callers → tests +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklists + +### Rename Symbol + +``` +- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits +- [ ] Review graph edits (high confidence) and ast_search edits (review carefully) +- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits +- [ ] gitnexus_detect_changes() — verify only expected files changed +- [ ] Run tests for affected processes +``` + +### Extract Module + +``` +- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs +- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers +- [ ] Define new module interface +- [ ] Extract code, update imports +- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] Run tests for affected processes +``` + +### Split Function/Service + +``` +- [ ] gitnexus_context({name: target}) — understand all callees +- [ ] Group callees by responsibility +- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update +- [ ] Create new functions/services +- [ ] Update callers +- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] Run tests for affected processes +``` + +## Tools + +**gitnexus_rename** — automated multi-file rename: + +``` +gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) +→ 12 edits across 8 files +→ 10 graph edits (high confidence), 2 ast_search edits (review) +→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}] +``` + +**gitnexus_impact** — map all dependents first: + +``` +gitnexus_impact({target: "validateUser", direction: "upstream"}) +→ d=1: loginHandler, apiMiddleware, testUtils +→ Affected Processes: LoginFlow, TokenRefresh +``` + +**gitnexus_detect_changes** — verify your changes after refactoring: + +``` +gitnexus_detect_changes({scope: "all"}) +→ Changed: 8 files, 12 symbols +→ Affected processes: LoginFlow, TokenRefresh +→ Risk: MEDIUM +``` + +**gitnexus_cypher** — custom reference queries: + +```cypher +MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"}) +RETURN caller.name, caller.filePath ORDER BY caller.filePath +``` + +## Risk Rules + +| Risk Factor | Mitigation | +| ------------------- | ----------------------------------------- | +| Many callers (>5) | Use gitnexus_rename for automated updates | +| Cross-area refs | Use detect_changes after to verify scope | +| String/dynamic refs | gitnexus_query to find them | +| External/public API | Version and deprecate properly | + +## Example: Rename `validateUser` to `authenticateUser` + +``` +1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) + → 12 edits: 10 graph (safe), 2 ast_search (review) + → Files: validator.ts, login.ts, middleware.ts, config.json... + +2. Review ast_search edits (config.json: dynamic reference!) + +3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false}) + → Applied 12 edits across 8 files + +4. gitnexus_detect_changes({scope: "all"}) + → Affected: LoginFlow, TokenRefresh + → Risk: MEDIUM — run tests for these flows +``` diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..7426fb3 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +allowBuilds: + esbuild: true + msw: true + sharp: true diff --git a/src/api/finance-api.ts b/src/api/finance-api.ts index 47db464..c87be53 100644 --- a/src/api/finance-api.ts +++ b/src/api/finance-api.ts @@ -14,6 +14,9 @@ import type { DepositWithdrawConfig, DepositWithdrawConfigDto, FinanceCurrencyConfigDto, + FinanceDepositConfigDto, + FinanceDepositFieldsDto, + FinanceDepositMethodDto, FinanceOrderItemDto, FinanceOrderList, FinanceOrderListDto, @@ -80,7 +83,7 @@ function normalizePayChannel(dto: FinancePayChannelDto) { code: dto.code, name: dto.name, sort: Number.isFinite(dto.sort) ? dto.sort : 0, - status: dto.status, + status: typeof dto.status === 'number' ? dto.status : 1, tierIds: Array.isArray(dto.tier_ids) ? dto.tier_ids : [], } } @@ -91,6 +94,8 @@ function normalizeWithdrawBank(dto: FinanceWithdrawBankDto, index: number) { return { code, + currencyCode: dto.currency_code ?? null, + gatewayCode: dto.gateway_code ?? null, label, sort: typeof dto.sort === 'number' && Number.isFinite(dto.sort) @@ -100,15 +105,68 @@ function normalizeWithdrawBank(dto: FinanceWithdrawBankDto, index: number) { } } +function normalizeDepositMethod(dto: FinanceDepositMethodDto) { + return { + code: dto.code, + currencyCode: dto.currency_code, + gatewayCode: dto.gateway_code, + name: dto.name, + requiresBank: dto.requires_bank, + requiresBankAccount: dto.requires_bank_account, + requiresDepositorName: dto.requires_depositor_name, + requiresFromAddress: dto.requires_from_address, + } +} + +function normalizeDepositFields(dto: FinanceDepositFieldsDto | undefined = {}) { + return { + depositBankAccountParam: + dto.deposit_bank_account_param ?? 'deposit_bank_account', + depositBankParam: dto.deposit_bank_param ?? 'deposit_bank', + depositFromAddressParam: + dto.deposit_from_address_param ?? 'deposit_from_address', + depositNameServerSide: dto.deposit_name_server_side ?? false, + paymentTypeParam: dto.payment_type_param ?? 'payment_type', + requireChannelCode: dto.require_channel_code ?? true, + requireIdempotencyKey: dto.require_idempotency_key ?? true, + requirePaymentType: dto.require_payment_type ?? false, + } +} + +function normalizeDepositConfig(dto: FinanceDepositConfigDto | undefined) { + return { + banks: (dto?.banks ?? []).map(normalizeWithdrawBank), + defaultChannelByCurrency: dto?.default_channel_by_currency ?? {}, + fields: normalizeDepositFields(dto?.fields), + methods: (dto?.methods ?? []).map(normalizeDepositMethod), + } +} + +function normalizeWithdrawFields(dto: FinanceWithdrawConfigDto['fields'] = {}) { + return { + receiveTypeBankOnly: dto.receive_type_bank_only ?? true, + requireBankBranch: dto.require_bank_branch ?? false, + requireBankCode: dto.require_bank_code ?? true, + requireChannelCode: dto.require_channel_code ?? true, + requireReceiveAccount: dto.require_receive_account ?? true, + requireReceiverEmail: dto.require_receiver_email ?? true, + requireReceiverMobile: dto.require_receiver_mobile ?? true, + requireReceiverName: dto.require_receiver_name ?? true, + } +} + function normalizeWithdrawConfig(dto: FinanceWithdrawConfigDto) { return { banks: (dto.banks ?? []).map(normalizeWithdrawBank), feeNote: dto.fee_note, + fields: normalizeWithdrawFields(dto.fields), minBank: dto.min_bank, minEwallet: dto.min_ewallet, + payChannels: (dto.pay_channels ?? []).map(normalizePayChannel), processingNote: dto.processing_note, rateHint: dto.rate_hint, rateMode: dto.rate_mode, + reviewThresholdCoin: dto.review_threshold_coin ?? '0', } } @@ -117,6 +175,8 @@ function normalizeDepositWithdrawConfig( ): DepositWithdrawConfig { return { currencies: (dto.currencies ?? []).map(normalizeCurrency), + defaultDepositChannelCode: dto.default_deposit_channel_code ?? '', + deposit: normalizeDepositConfig(dto.deposit), payChannels: (dto.pay_channels ?? []).map(normalizePayChannel), platformCoinLabel: dto.platform_coin_label, rates: (dto.rates ?? []).map(normalizeRate), diff --git a/src/constants/game.ts b/src/constants/game.ts index 3802df9..46abab6 100644 --- a/src/constants/game.ts +++ b/src/constants/game.ts @@ -285,6 +285,22 @@ export const DEFAULT_WITHDRAW_CONFIG: DepositWithdrawConfig = { withdrawCoinsPerFiatValue: 100, }, ], + defaultDepositChannelCode: '', + deposit: { + banks: [], + defaultChannelByCurrency: {}, + fields: { + depositBankAccountParam: 'deposit_bank_account', + depositBankParam: 'deposit_bank', + depositFromAddressParam: 'deposit_from_address', + depositNameServerSide: false, + paymentTypeParam: 'payment_type', + requireChannelCode: true, + requireIdempotencyKey: true, + requirePaymentType: false, + }, + methods: [], + }, payChannels: [], platformCoinLabel: '钻石', rates: [ @@ -297,11 +313,23 @@ export const DEFAULT_WITHDRAW_CONFIG: DepositWithdrawConfig = { withdraw: { banks: [], feeNote: 'RM10 - RM99.99 之间的交易将收取最低RM 1的提现手续费', + fields: { + receiveTypeBankOnly: true, + requireBankBranch: false, + requireBankCode: true, + requireChannelCode: true, + requireReceiveAccount: true, + requireReceiverEmail: true, + requireReceiverMobile: true, + requireReceiverName: true, + }, minBank: '10', minEwallet: '10', + payChannels: [], processingNote: '30s即可到账', rateHint: '汇率为参考价格,实际以提现时为准。', rateMode: 'fixed', + reviewThresholdCoin: '0', }, } diff --git a/src/features/game/components/desktop/desktop-topup.tsx b/src/features/game/components/desktop/desktop-topup.tsx index ffbe6c8..0f9dae6 100644 --- a/src/features/game/components/desktop/desktop-topup.tsx +++ b/src/features/game/components/desktop/desktop-topup.tsx @@ -3,7 +3,9 @@ import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { createDeposit } from '@/api' import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator' +import { DEFAULT_WITHDRAW_CONFIG } from '@/constants' import { useDepositTierList } from '@/hooks/use-deposit-tier-list' +import { useDepositWithdrawConfig } from '@/hooks/use-deposit-withdraw-config' import { notify } from '@/lib/notify' import { cn } from '@/lib/utils' import type { DepositTierItem } from '@/type' @@ -17,21 +19,39 @@ function formatNumber(value: number) { function DesktopTopup() { const { t } = useTranslation() + const depositWithdrawConfigQuery = useDepositWithdrawConfig() const tierListQuery = useDepositTierList() + const depositWithdrawConfig = + depositWithdrawConfigQuery.data ?? DEFAULT_WITHDRAW_CONFIG + const isLoading = + tierListQuery.isLoading || depositWithdrawConfigQuery.isLoading + const isError = tierListQuery.isError || depositWithdrawConfigQuery.isError const tiers = tierListQuery.data ?? [] const createDepositInFlightRef = useRef(false) const pendingPayWindowRef = useRef(null) const createDepositMutation = useMutation({ mutationFn: ({ channelCode, + depositBank, + depositBankAccount, + depositFromAddress, + paymentType, tierId, }: { channelCode: string + depositBank?: string + depositBankAccount?: string + depositFromAddress?: string + paymentType: string tierId: string }) => createDeposit({ channel_code: channelCode, + deposit_bank: depositBank, + deposit_bank_account: depositBankAccount, + deposit_from_address: depositFromAddress, idempotency_key: String(Date.now()), + payment_type: paymentType, tier_id: tierId, }), }) @@ -41,13 +61,34 @@ function DesktopTopup() { return } - const channelCode = tier.payChannelCode ?? tier.channels[0]?.code ?? '' + const channelCode = + tier.payChannelCode ?? + tier.channels[0]?.code ?? + (tier.currency + ? depositWithdrawConfig.deposit.defaultChannelByCurrency[tier.currency] + : undefined) ?? + depositWithdrawConfig.defaultDepositChannelCode + const selectedPaymentMethod = + depositWithdrawConfig.deposit.methods.find( + (method) => method.currencyCode === tier.currency, + ) ?? depositWithdrawConfig.deposit.methods[0] + const paymentType = selectedPaymentMethod?.code ?? '' + const depositBank = selectedPaymentMethod?.requiresBank + ? depositWithdrawConfig.deposit.banks.find( + (bank) => bank.currencyCode === tier.currency, + )?.code + : undefined if (!channelCode) { notify.error(t('commonUi.toast.configError')) return } + if (!paymentType) { + notify.error(t('commonUi.toast.configError')) + return + } + createDepositInFlightRef.current = true const payWindow = window.open('', '_blank') @@ -63,6 +104,8 @@ function DesktopTopup() { try { const result = await createDepositMutation.mutateAsync({ channelCode, + depositBank, + paymentType, tierId: tier.id, }) const payUrl = result.pay_url.trim() @@ -105,12 +148,12 @@ function DesktopTopup() { - {tierListQuery.isLoading ? ( + {isLoading ? ( - ) : tierListQuery.isError ? ( + ) : isError ? (
{t('gameDesktop.topup.tier.failed')}
diff --git a/src/features/game/components/mobile/mobile-topup.tsx b/src/features/game/components/mobile/mobile-topup.tsx index aa38889..f4e3859 100644 --- a/src/features/game/components/mobile/mobile-topup.tsx +++ b/src/features/game/components/mobile/mobile-topup.tsx @@ -3,7 +3,9 @@ import { useRef } from 'react' import { useTranslation } from 'react-i18next' import { createDeposit } from '@/api' import { DataLoadingIndicator } from '@/components/ui/data-loading-indicator' +import { DEFAULT_WITHDRAW_CONFIG } from '@/constants' import { useDepositTierList } from '@/hooks/use-deposit-tier-list' +import { useDepositWithdrawConfig } from '@/hooks/use-deposit-withdraw-config' import { notify } from '@/lib/notify' import { cn } from '@/lib/utils' import type { DepositTierItem } from '@/type' @@ -17,21 +19,39 @@ function formatNumber(value: number) { function MobileTopup() { const { t } = useTranslation() + const depositWithdrawConfigQuery = useDepositWithdrawConfig() const tierListQuery = useDepositTierList() + const depositWithdrawConfig = + depositWithdrawConfigQuery.data ?? DEFAULT_WITHDRAW_CONFIG + const isLoading = + tierListQuery.isLoading || depositWithdrawConfigQuery.isLoading + const isError = tierListQuery.isError || depositWithdrawConfigQuery.isError const tiers = tierListQuery.data ?? [] const createDepositInFlightRef = useRef(false) const pendingPayWindowRef = useRef(null) const createDepositMutation = useMutation({ mutationFn: ({ channelCode, + depositBank, + depositBankAccount, + depositFromAddress, + paymentType, tierId, }: { channelCode: string + depositBank?: string + depositBankAccount?: string + depositFromAddress?: string + paymentType: string tierId: string }) => createDeposit({ channel_code: channelCode, + deposit_bank: depositBank, + deposit_bank_account: depositBankAccount, + deposit_from_address: depositFromAddress, idempotency_key: String(Date.now()), + payment_type: paymentType, tier_id: tierId, }), }) @@ -41,13 +61,34 @@ function MobileTopup() { return } - const channelCode = tier.payChannelCode ?? tier.channels[0]?.code ?? '' + const channelCode = + tier.payChannelCode ?? + tier.channels[0]?.code ?? + (tier.currency + ? depositWithdrawConfig.deposit.defaultChannelByCurrency[tier.currency] + : undefined) ?? + depositWithdrawConfig.defaultDepositChannelCode + const selectedPaymentMethod = + depositWithdrawConfig.deposit.methods.find( + (method) => method.currencyCode === tier.currency, + ) ?? depositWithdrawConfig.deposit.methods[0] + const paymentType = selectedPaymentMethod?.code ?? '' + const depositBank = selectedPaymentMethod?.requiresBank + ? depositWithdrawConfig.deposit.banks.find( + (bank) => bank.currencyCode === tier.currency, + )?.code + : undefined if (!channelCode) { notify.error(t('commonUi.toast.configError')) return } + if (!paymentType) { + notify.error(t('commonUi.toast.configError')) + return + } + createDepositInFlightRef.current = true const payWindow = window.open('', '_blank') @@ -63,6 +104,8 @@ function MobileTopup() { try { const result = await createDepositMutation.mutateAsync({ channelCode, + depositBank, + paymentType, tierId: tier.id, }) const payUrl = result.pay_url.trim() @@ -105,12 +148,12 @@ function MobileTopup() { - {tierListQuery.isLoading ? ( + {isLoading ? ( - ) : tierListQuery.isError ? ( + ) : isError ? (
{t('gameDesktop.topup.tier.failed')}
diff --git a/src/hooks/use-withdraw-vm.ts b/src/hooks/use-withdraw-vm.ts index b470d38..59781ab 100644 --- a/src/hooks/use-withdraw-vm.ts +++ b/src/hooks/use-withdraw-vm.ts @@ -85,6 +85,7 @@ export function useWithdrawVm() { withdraw: { ...baseConfig.withdraw, banks: baseConfig.withdraw.banks, + payChannels: baseConfig.withdraw.payChannels, }, } }, [withdrawConfigQuery.data]) @@ -109,17 +110,25 @@ export function useWithdrawVm() { const selectedRate = selectedCurrency.withdrawCoinsPerFiatValue || 1 const sortedPayChannels = useMemo( () => - [...config.payChannels] + [ + ...(config.withdraw.payChannels.length > 0 + ? config.withdraw.payChannels + : config.payChannels), + ] .filter((channel) => channel.status === 1) .sort((left, right) => left.sort - right.sort), - [config.payChannels], + [config.payChannels, config.withdraw.payChannels], ) const sortedBanks = useMemo( () => [...config.withdraw.banks] - .filter((bank) => bank.status === 1) + .filter( + (bank) => + bank.status === 1 && + (!bank.currencyCode || bank.currencyCode === selectedCurrency.code), + ) .sort((left, right) => left.sort - right.sort), - [config.withdraw.banks], + [config.withdraw.banks, selectedCurrency.code], ) const availableBalance = Number(currentUser?.coin ?? 0) const maxWithdrawAmount = Math.max(0, Math.floor(availableBalance)) diff --git a/src/type/api.type.ts b/src/type/api.type.ts index 7bc6e09..68cff1f 100644 --- a/src/type/api.type.ts +++ b/src/type/api.type.ts @@ -28,12 +28,14 @@ export interface FinancePayChannelDto { code: string name: string sort: number - status: number - tier_ids: number[] + status?: number + tier_ids?: number[] } export interface FinanceWithdrawBankDto { code?: string + currency_code?: string + gateway_code?: string id?: string label?: string name?: string @@ -41,18 +43,63 @@ export interface FinanceWithdrawBankDto { status?: number } +export interface FinanceDepositMethodDto { + code: string + currency_code: string + gateway_code: string + name: string + requires_bank: boolean + requires_bank_account: boolean + requires_depositor_name: boolean + requires_from_address: boolean +} + +export interface FinanceDepositFieldsDto { + deposit_bank_account_param?: string + deposit_bank_param?: string + deposit_from_address_param?: string + deposit_name_server_side?: boolean + payment_type_param?: string + require_channel_code?: boolean + require_idempotency_key?: boolean + require_payment_type?: boolean +} + +export interface FinanceDepositConfigDto { + banks?: FinanceWithdrawBankDto[] + default_channel_by_currency?: Record + fields?: FinanceDepositFieldsDto + methods?: FinanceDepositMethodDto[] +} + +export interface FinanceWithdrawFieldsDto { + receive_type_bank_only?: boolean + require_bank_branch?: boolean + require_bank_code?: boolean + require_channel_code?: boolean + require_receive_account?: boolean + require_receiver_email?: boolean + require_receiver_mobile?: boolean + require_receiver_name?: boolean +} + export interface FinanceWithdrawConfigDto { banks: FinanceWithdrawBankDto[] fee_note: string + fields?: FinanceWithdrawFieldsDto min_bank: string min_ewallet: string + pay_channels?: FinancePayChannelDto[] processing_note: string rate_hint: string rate_mode: 'fixed' | 'live' | (string & {}) + review_threshold_coin?: string } export interface DepositWithdrawConfigDto { currencies: FinanceCurrencyConfigDto[] + default_deposit_channel_code?: string + deposit?: FinanceDepositConfigDto pay_channels: FinancePayChannelDto[] platform_coin_label: string rates: FinanceRateConfigDto[] @@ -104,23 +151,70 @@ export interface FinancePayChannel { export interface FinanceWithdrawBank { code: string + currencyCode: string | null + gatewayCode: string | null label: string sort: number status: number } +export interface FinanceDepositMethod { + code: string + currencyCode: string + gatewayCode: string + name: string + requiresBank: boolean + requiresBankAccount: boolean + requiresDepositorName: boolean + requiresFromAddress: boolean +} + +export interface FinanceDepositFields { + depositBankAccountParam: string + depositBankParam: string + depositFromAddressParam: string + depositNameServerSide: boolean + paymentTypeParam: string + requireChannelCode: boolean + requireIdempotencyKey: boolean + requirePaymentType: boolean +} + +export interface FinanceDepositConfig { + banks: FinanceWithdrawBank[] + defaultChannelByCurrency: Record + fields: FinanceDepositFields + methods: FinanceDepositMethod[] +} + +export interface FinanceWithdrawFields { + receiveTypeBankOnly: boolean + requireBankBranch: boolean + requireBankCode: boolean + requireChannelCode: boolean + requireReceiveAccount: boolean + requireReceiverEmail: boolean + requireReceiverMobile: boolean + requireReceiverName: boolean +} + export interface FinanceWithdrawConfig { banks: FinanceWithdrawBank[] feeNote: string + fields: FinanceWithdrawFields minBank: string minEwallet: string + payChannels: FinancePayChannel[] processingNote: string rateHint: string rateMode: FinanceWithdrawConfigDto['rate_mode'] + reviewThresholdCoin: string } export interface DepositWithdrawConfig { currencies: FinanceCurrencyConfig[] + defaultDepositChannelCode: string + deposit: FinanceDepositConfig payChannels: FinancePayChannel[] platformCoinLabel: string rates: FinanceRateConfig[] @@ -147,7 +241,11 @@ export interface DepositTierItem { export interface DepositCreateRequestDto { channel_code: string + deposit_bank?: string + deposit_bank_account?: string + deposit_from_address?: string idempotency_key: string + payment_type: string tier_id: string }