diff --git a/contracts/admin-openapi.json b/contracts/admin-openapi.json index 4fdee98..8900b6d 100644 --- a/contracts/admin-openapi.json +++ b/contracts/admin-openapi.json @@ -1622,6 +1622,129 @@ "x-permissions": ["coin-seller:update"] } }, + "/admin/coin-sellers/{user_id}/sub-sellers": { + "get": { + "operationId": "listCoinSellerSubSellers", + "parameters": [ + { + "in": "path", + "name": "user_id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/CoinSellerPageResponse" + } + }, + "x-permission": "coin-seller:view", + "x-permissions": ["coin-seller:view"] + } + }, + "/admin/coin-seller-sub-applications": { + "get": { + "operationId": "listCoinSellerSubApplications", + "parameters": [ + { + "$ref": "#/components/parameters/Page" + }, + { + "$ref": "#/components/parameters/PageSize" + }, + { + "$ref": "#/components/parameters/Keyword" + }, + { + "in": "query", + "name": "status", + "schema": { + "type": "string", + "enum": ["pending", "approved", "rejected"] + } + }, + { + "in": "query", + "name": "parent_user_id", + "schema": { + "type": "string" + } + }, + { + "in": "query", + "name": "target_user_id", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "$ref": "#/components/responses/CoinSellerSubApplicationPageResponse" + } + }, + "x-permission": "coin-seller-sub-application:view", + "x-permissions": ["coin-seller-sub-application:view"] + } + }, + "/admin/coin-seller-sub-applications/{application_id}/approve": { + "post": { + "operationId": "approveCoinSellerSubApplication", + "requestBody": { + "$ref": "#/components/requestBodies/CoinSellerSubApplicationReviewRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/CoinSellerSubApplicationReviewResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "application_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-permission": "coin-seller-sub-application:audit", + "x-permissions": ["coin-seller-sub-application:audit"] + } + }, + "/admin/coin-seller-sub-applications/{application_id}/reject": { + "post": { + "operationId": "rejectCoinSellerSubApplication", + "requestBody": { + "$ref": "#/components/requestBodies/CoinSellerSubApplicationReviewRequest" + }, + "responses": { + "200": { + "$ref": "#/components/responses/CoinSellerSubApplicationReviewResponse" + } + }, + "parameters": [ + { + "in": "path", + "name": "application_id", + "required": true, + "schema": { + "type": "string" + } + } + ], + "x-permission": "coin-seller-sub-application:audit", + "x-permissions": ["coin-seller-sub-application:audit"] + } + }, "/admin/coin-sellers/{user_id}/stock-credits": { "post": { "operationId": "creditCoinSellerStock", @@ -2887,7 +3010,12 @@ "in": "query", "schema": { "type": "string", - "enum": ["admin_stock_credit", "seller_transfer", "sub_seller_transfer", "salary_transfer_received"] + "enum": [ + "admin_stock_credit", + "seller_transfer", + "sub_seller_transfer", + "salary_transfer_received" + ] } }, { @@ -2975,7 +3103,12 @@ "in": "query", "schema": { "type": "string", - "enum": ["admin_stock_credit", "seller_transfer", "sub_seller_transfer", "salary_transfer_received"] + "enum": [ + "admin_stock_credit", + "seller_transfer", + "sub_seller_transfer", + "salary_transfer_received" + ] } }, { @@ -3083,7 +3216,11 @@ { "in": "query", "name": "keyword", "schema": { "type": "string" } }, { "in": "query", "name": "status", "schema": { "type": "string", "enum": ["active", "disabled"] } }, { "in": "query", "name": "page", "schema": { "type": "integer", "minimum": 1 } }, - { "in": "query", "name": "page_size", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } + { + "in": "query", + "name": "page_size", + "schema": { "type": "integer", "minimum": 1, "maximum": 100 } + } ], "responses": { "200": { @@ -3161,7 +3298,11 @@ { "in": "query", "name": "keyword", "schema": { "type": "string" } }, { "in": "query", "name": "status", "schema": { "type": "string", "enum": ["active", "disabled"] } }, { "in": "query", "name": "page", "schema": { "type": "integer", "minimum": 1 } }, - { "in": "query", "name": "page_size", "schema": { "type": "integer", "minimum": 1, "maximum": 100 } } + { + "in": "query", + "name": "page_size", + "schema": { "type": "integer", "minimum": 1, "maximum": 100 } + } ], "responses": { "200": { @@ -3718,7 +3859,12 @@ { "in": "query", "name": "end_ms", "schema": { "type": "integer", "format": "int64" } }, { "in": "query", "name": "app_codes", "schema": { "type": "string" } }, { "in": "query", "name": "region_id", "schema": { "type": "integer", "format": "int64" } }, - { "in": "query", "name": "region_ids", "description": "逗号分隔的大区 ID;用于 Social BI 顶栏多选区域。", "schema": { "type": "string" } }, + { + "in": "query", + "name": "region_ids", + "description": "逗号分隔的大区 ID;用于 Social BI 顶栏多选区域。", + "schema": { "type": "string" } + }, { "in": "query", "name": "section", @@ -6728,6 +6874,16 @@ } } }, + "CoinSellerSubApplicationReviewRequest": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CoinSellerSubApplicationReviewPayload" + } + } + } + }, "CountryCreateRequest": { "required": true, "content": { @@ -7061,6 +7217,26 @@ } } }, + "CoinSellerSubApplicationPageResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseCoinSellerSubApplicationPage" + } + } + } + }, + "CoinSellerSubApplicationReviewResponse": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponseCoinSellerSubApplicationReview" + } + } + } + }, "CoinSellerLedgerPageResponse": { "description": "OK", "content": { @@ -7517,6 +7693,27 @@ } } }, + "ApiPageCoinSellerSubApplication": { + "type": "object", + "required": ["items", "page", "pageSize", "total"], + "properties": { + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CoinSellerSubApplication" + } + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "total": { + "type": "integer" + } + } + }, "ApiPageCoinSellerLedger": { "type": "object", "required": ["items", "page", "pageSize", "total"], @@ -7894,6 +8091,36 @@ } ] }, + "ApiResponseCoinSellerSubApplicationPage": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/ApiPageCoinSellerSubApplication" + } + } + } + ] + }, + "ApiResponseCoinSellerSubApplicationReview": { + "allOf": [ + { + "$ref": "#/components/schemas/Envelope" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/CoinSellerSubApplicationReviewResult" + } + } + } + ] + }, "ApiResponseCoinSellerLedgerPage": { "allOf": [ { @@ -8780,6 +9007,9 @@ "type": "string", "enum": ["active", "disabled"] }, + "subCoinSellerCount": { + "type": "integer" + }, "totalRechargeUsdtMicro": { "type": "integer" }, @@ -8794,6 +9024,204 @@ } } }, + "CoinSellerSubApplicationUser": { + "type": "object", + "required": ["userId"], + "properties": { + "avatar": { + "type": "string" + }, + "displayUserId": { + "type": "string" + }, + "regionId": { + "type": "integer" + }, + "regionName": { + "type": "string" + }, + "userId": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "CoinSellerSubApplication": { + "type": "object", + "required": ["applicationId", "parent", "target", "status"], + "properties": { + "applicationId": { + "type": "string" + }, + "commandId": { + "type": "string" + }, + "createdAtMs": { + "type": "integer" + }, + "parent": { + "$ref": "#/components/schemas/CoinSellerSubApplicationUser" + }, + "relationId": { + "type": "string" + }, + "reviewedAtMs": { + "type": "integer" + }, + "reviewedByAdminId": { + "type": "string" + }, + "reviewReason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": ["pending", "approved", "rejected"] + }, + "target": { + "$ref": "#/components/schemas/CoinSellerSubApplicationUser" + }, + "updatedAtMs": { + "type": "integer" + } + } + }, + "CoinSellerSubApplicationReviewApplication": { + "type": "object", + "required": ["applicationId", "parentUserId", "targetUserId", "status"], + "properties": { + "applicationId": { + "type": "string" + }, + "commandId": { + "type": "string" + }, + "createdAtMs": { + "type": "integer" + }, + "parentUserId": { + "type": "string" + }, + "relationId": { + "type": "string" + }, + "reviewedAtMs": { + "type": "integer" + }, + "reviewedByAdminId": { + "type": "string" + }, + "reviewReason": { + "type": "string" + }, + "status": { + "type": "string", + "enum": ["pending", "approved", "rejected"] + }, + "targetUserId": { + "type": "string" + }, + "updatedAtMs": { + "type": "integer" + } + } + }, + "CoinSellerSubRelation": { + "type": "object", + "required": ["relationId", "parentUserId", "childUserId", "status"], + "properties": { + "childUserId": { + "type": "string" + }, + "createdAtMs": { + "type": "integer" + }, + "createdByUserId": { + "type": "string" + }, + "parentUserId": { + "type": "string" + }, + "relationId": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAtMs": { + "type": "integer" + } + } + }, + "CoinSellerSubApplicationReviewChild": { + "type": "object", + "required": ["userId"], + "properties": { + "avatar": { + "type": "string" + }, + "canManageSubCoinSellers": { + "type": "boolean" + }, + "contactInfo": { + "type": "string" + }, + "displayUserId": { + "type": "string" + }, + "merchantAssetType": { + "type": "string" + }, + "regionId": { + "type": "integer" + }, + "regionName": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAtMs": { + "type": "integer" + }, + "userId": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "CoinSellerSubApplicationReviewPayload": { + "type": "object", + "required": ["commandId"], + "properties": { + "commandId": { + "type": "string", + "maxLength": 120 + }, + "reason": { + "type": "string", + "maxLength": 200 + } + } + }, + "CoinSellerSubApplicationReviewResult": { + "type": "object", + "properties": { + "application": { + "$ref": "#/components/schemas/CoinSellerSubApplicationReviewApplication" + }, + "child": { + "$ref": "#/components/schemas/CoinSellerSubApplicationReviewChild" + }, + "relation": { + "$ref": "#/components/schemas/CoinSellerSubRelation" + } + } + }, "CoinLedgerUser": { "type": "object", "required": ["userId"], @@ -8814,7 +9242,19 @@ }, "CoinSellerLedger": { "type": "object", - "required": ["entryId", "transactionId", "ledgerType", "bizType", "seller", "receiver", "amount", "direction", "availableDelta", "sellerBalanceAfter", "createdAtMs"], + "required": [ + "entryId", + "transactionId", + "ledgerType", + "bizType", + "seller", + "receiver", + "amount", + "direction", + "availableDelta", + "sellerBalanceAfter", + "createdAtMs" + ], "properties": { "amount": { "type": "integer", @@ -8857,7 +9297,12 @@ }, "ledgerType": { "type": "string", - "enum": ["admin_stock_credit", "seller_transfer", "sub_seller_transfer", "salary_transfer_received"] + "enum": [ + "admin_stock_credit", + "seller_transfer", + "sub_seller_transfer", + "salary_transfer_received" + ] }, "metadata": { "type": "object", @@ -10132,6 +10577,13 @@ "externalOrderNo": { "type": "string" }, + "providerCurrencyCode": { + "type": "string" + }, + "providerAmountMinor": { + "type": "integer", + "minimum": 1 + }, "chain": { "type": "string", "enum": ["TRON", "BSC"] @@ -10210,6 +10662,13 @@ "usdMinorAmount": { "type": "integer", "minimum": 0 + }, + "providerCurrencyCode": { + "type": "string" + }, + "providerAmountMinor": { + "type": "integer", + "minimum": 1 } } }, diff --git a/finance/src/api.test.js b/finance/src/api.test.js index 8175a5c..9133dd7 100644 --- a/finance/src/api.test.js +++ b/finance/src/api.test.js @@ -113,14 +113,18 @@ test("finance coin seller recharge order APIs use generated endpoint paths", asy appCode: "lalu", coinAmount: 100000, externalOrderNo: "MIFA-001", + providerAmountMinor: 125050, providerCode: "mifapay", + providerCurrencyCode: "PHP", targetUserId: "10001", usdAmount: 10 }); await verifyFinanceCoinSellerRechargeReceipt({ appCode: "lalu", externalOrderNo: "MIFA-001", + providerAmountMinor: 125050, providerCode: "mifapay", + providerCurrencyCode: "PHP", usdAmount: 10 }); await verifyFinanceCoinSellerRechargeOrder("order-1"); @@ -132,13 +136,24 @@ test("finance coin seller recharge order APIs use generated endpoint paths", asy expect(calls[0][1]?.method).toBe("GET"); expect(String(calls[1][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges"); expect(calls[1][1]?.method).toBe("POST"); - expect(JSON.parse(String(calls[1][1]?.body))).toMatchObject({ externalOrderNo: "MIFA-001", providerCode: "mifapay" }); + expect(JSON.parse(String(calls[1][1]?.body))).toEqual({ + appCode: "lalu", + coinAmount: 100000, + externalOrderNo: "MIFA-001", + providerAmountMinor: 125050, + providerCode: "mifapay", + providerCurrencyCode: "PHP", + targetUserId: "10001", + usdAmount: 10 + }); expect(String(calls[2][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/receipt-verifications"); expect(calls[2][1]?.method).toBe("POST"); expect(JSON.parse(String(calls[2][1]?.body))).toEqual({ appCode: "lalu", externalOrderNo: "MIFA-001", + providerAmountMinor: 125050, providerCode: "mifapay", + providerCurrencyCode: "PHP", usdAmount: 10 }); expect(String(calls[3][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/verify"); diff --git a/finance/src/components/FinanceCoinSellerRechargeOrderList.jsx b/finance/src/components/FinanceCoinSellerRechargeOrderList.jsx index d54f93e..dd85083 100644 --- a/finance/src/components/FinanceCoinSellerRechargeOrderList.jsx +++ b/finance/src/components/FinanceCoinSellerRechargeOrderList.jsx @@ -3,6 +3,7 @@ import FactCheckOutlined from "@mui/icons-material/FactCheckOutlined"; import PaidOutlined from "@mui/icons-material/PaidOutlined"; import RefreshOutlined from "@mui/icons-material/RefreshOutlined"; import Alert from "@mui/material/Alert"; +import Autocomplete from "@mui/material/Autocomplete"; import Button from "@mui/material/Button"; import Dialog from "@mui/material/Dialog"; import DialogActions from "@mui/material/DialogActions"; @@ -14,6 +15,7 @@ import TextField from "@mui/material/TextField"; import { useState } from "react"; import { COIN_SELLER_RECHARGE_CHAINS, + COIN_SELLER_RECHARGE_PROVIDER_CURRENCIES, COIN_SELLER_RECHARGE_PROVIDERS, COIN_SELLER_RECHARGE_STATUS, DEFAULT_COIN_SELLER_RECHARGE_ORDER_FORM, @@ -78,7 +80,9 @@ export function FinanceCoinSellerRechargeOrderList({ setForm((current) => ({ ...current, chain: "", + providerAmount: "", providerCode: value, + providerCurrencyCode: "", })); setFormError(""); setReceiptVerification(null); @@ -201,7 +205,12 @@ export function FinanceCoinSellerRechargeOrderList({ } variant="outlined" onClick={onReload}> 刷新 - } variant="contained" onClick={openCreate}> + } + variant="contained" + onClick={openCreate} + > 创建币商充值 @@ -234,7 +243,10 @@ export function FinanceCoinSellerRechargeOrderList({