自动充值系统
This commit is contained in:
parent
ee023df614
commit
7711f2a9ee
@ -3578,6 +3578,148 @@
|
||||
"x-permissions": ["finance-withdrawal:audit", "finance-application:audit"]
|
||||
}
|
||||
},
|
||||
"/admin/finance/orders/coin-seller-recharges": {
|
||||
"get": {
|
||||
"operationId": "listFinanceCoinSellerRechargeOrders",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "query",
|
||||
"name": "app_code",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "provider_code",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "status",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "verify_status",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "grant_status",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "keyword",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "page",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "page_size",
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/FinanceCoinSellerRechargeOrderPageResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "finance-order:coin-seller-recharge:view",
|
||||
"x-permissions": ["finance-order:coin-seller-recharge:view"]
|
||||
},
|
||||
"post": {
|
||||
"operationId": "createFinanceCoinSellerRechargeOrder",
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/FinanceCoinSellerRechargeOrderRequest"
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"$ref": "#/components/responses/FinanceCoinSellerRechargeOrderResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "finance-order:coin-seller-recharge:create",
|
||||
"x-permissions": ["finance-order:coin-seller-recharge:create"]
|
||||
}
|
||||
},
|
||||
"/admin/finance/orders/coin-seller-recharges/receipt-verifications": {
|
||||
"post": {
|
||||
"operationId": "verifyFinanceCoinSellerRechargeReceipt",
|
||||
"requestBody": {
|
||||
"$ref": "#/components/requestBodies/FinanceCoinSellerRechargeReceiptVerificationRequest"
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/FinanceCoinSellerRechargeReceiptVerificationResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "finance-order:coin-seller-recharge:verify",
|
||||
"x-permissions": ["finance-order:coin-seller-recharge:verify"]
|
||||
}
|
||||
},
|
||||
"/admin/finance/orders/coin-seller-recharges/{order_id}/verify": {
|
||||
"post": {
|
||||
"operationId": "verifyFinanceCoinSellerRechargeOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "order_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/FinanceCoinSellerRechargeOrderResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "finance-order:coin-seller-recharge:verify",
|
||||
"x-permissions": ["finance-order:coin-seller-recharge:verify"]
|
||||
}
|
||||
},
|
||||
"/admin/finance/orders/coin-seller-recharges/{order_id}/grant": {
|
||||
"post": {
|
||||
"operationId": "grantFinanceCoinSellerRechargeOrder",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "order_id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"$ref": "#/components/responses/FinanceCoinSellerRechargeOrderResponse"
|
||||
}
|
||||
},
|
||||
"x-permission": "finance-order:coin-seller-recharge:grant",
|
||||
"x-permissions": ["finance-order:coin-seller-recharge:grant"]
|
||||
}
|
||||
},
|
||||
"/admin/payment/temporary-links": {
|
||||
"get": {
|
||||
"operationId": "listTemporaryPaymentLinks",
|
||||
@ -6073,6 +6215,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeOrderRequest": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FinanceCoinSellerRechargeOrderInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeReceiptVerificationRequest": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/FinanceCoinSellerRechargeReceiptVerificationInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceApplicationAuditRequest": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
@ -6345,6 +6505,36 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeOrderPageResponse": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponseFinanceCoinSellerRechargeOrderPage"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeOrderResponse": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponseFinanceCoinSellerRechargeOrder"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeReceiptVerificationResponse": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponseFinanceCoinSellerRechargeReceiptVerification"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceWithdrawalApplicationPageResponse": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
@ -6694,6 +6884,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiPageFinanceCoinSellerRechargeOrder": {
|
||||
"type": "object",
|
||||
"required": ["items", "page", "pageSize", "total"],
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/FinanceCoinSellerRechargeOrder"
|
||||
}
|
||||
},
|
||||
"page": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiPageFinanceWithdrawalApplication": {
|
||||
"type": "object",
|
||||
"required": ["items", "page", "pageSize", "total"],
|
||||
@ -6896,6 +7107,51 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApiResponseFinanceCoinSellerRechargeOrder": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Envelope"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/FinanceCoinSellerRechargeOrder"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApiResponseFinanceCoinSellerRechargeReceiptVerification": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Envelope"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/FinanceCoinSellerRechargeReceiptVerification"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApiResponseFinanceCoinSellerRechargeOrderPage": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Envelope"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/ApiPageFinanceCoinSellerRechargeOrder"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ApiResponseFinanceWithdrawalApplication": {
|
||||
"allOf": [
|
||||
{
|
||||
@ -8076,6 +8332,133 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeOrder": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"appCode",
|
||||
"targetUserId",
|
||||
"usdAmount",
|
||||
"coinAmount",
|
||||
"providerCode",
|
||||
"externalOrderNo",
|
||||
"status",
|
||||
"verifyStatus",
|
||||
"grantStatus",
|
||||
"createdAtMs"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"appCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetDisplayUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"usdAmount": {
|
||||
"type": "string"
|
||||
},
|
||||
"usdMinorAmount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"coinAmount": {
|
||||
"type": "integer"
|
||||
},
|
||||
"providerCode": {
|
||||
"type": "string",
|
||||
"enum": ["mifapay", "v5pay", "usdt"]
|
||||
},
|
||||
"chain": {
|
||||
"type": "string",
|
||||
"enum": ["TRON", "BSC"]
|
||||
},
|
||||
"externalOrderNo": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerCountryCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerCurrencyCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerAmountMinor": {
|
||||
"type": "integer"
|
||||
},
|
||||
"providerOrderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerPayType": {
|
||||
"type": "string"
|
||||
},
|
||||
"receiveAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"orderDate": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"verifyStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"grantStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"remark": {
|
||||
"type": "string"
|
||||
},
|
||||
"failureReason": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerPayloadJson": {
|
||||
"type": "string"
|
||||
},
|
||||
"walletCommandId": {
|
||||
"type": "string"
|
||||
},
|
||||
"walletTransactionId": {
|
||||
"type": "string"
|
||||
},
|
||||
"walletAssetType": {
|
||||
"type": "string"
|
||||
},
|
||||
"walletAmountDelta": {
|
||||
"type": "integer"
|
||||
},
|
||||
"walletBalanceAfter": {
|
||||
"type": "integer"
|
||||
},
|
||||
"operatorName": {
|
||||
"type": "string"
|
||||
},
|
||||
"verifiedByName": {
|
||||
"type": "string"
|
||||
},
|
||||
"grantedByName": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAtMs": {
|
||||
"type": "integer"
|
||||
},
|
||||
"verifiedAtMs": {
|
||||
"nullable": true,
|
||||
"type": "integer"
|
||||
},
|
||||
"grantedAtMs": {
|
||||
"nullable": true,
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceWithdrawalApplication": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -8190,6 +8573,112 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeOrderInput": {
|
||||
"type": "object",
|
||||
"required": ["appCode", "targetUserId", "usdAmount", "coinAmount", "providerCode", "externalOrderNo"],
|
||||
"properties": {
|
||||
"appCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"targetUserId": {
|
||||
"type": "string"
|
||||
},
|
||||
"usdAmount": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"coinAmount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"providerCode": {
|
||||
"type": "string",
|
||||
"enum": ["mifapay", "v5pay", "usdt"]
|
||||
},
|
||||
"externalOrderNo": {
|
||||
"type": "string"
|
||||
},
|
||||
"chain": {
|
||||
"type": "string",
|
||||
"enum": ["TRON", "BSC"]
|
||||
},
|
||||
"remark": {
|
||||
"type": "string",
|
||||
"maxLength": 512
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeReceiptVerification": {
|
||||
"type": "object",
|
||||
"required": ["verified"],
|
||||
"properties": {
|
||||
"verified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"providerCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"externalOrderNo": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerOrderId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"currencyCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerAmountMinor": {
|
||||
"type": "integer"
|
||||
},
|
||||
"chain": {
|
||||
"type": "string",
|
||||
"enum": ["TRON", "BSC"]
|
||||
},
|
||||
"receiveAddress": {
|
||||
"type": "string"
|
||||
},
|
||||
"verifiedAtMs": {
|
||||
"type": "integer"
|
||||
},
|
||||
"failureReason": {
|
||||
"type": "string"
|
||||
},
|
||||
"rawJson": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"FinanceCoinSellerRechargeReceiptVerificationInput": {
|
||||
"type": "object",
|
||||
"required": ["appCode", "providerCode", "externalOrderNo"],
|
||||
"properties": {
|
||||
"appCode": {
|
||||
"type": "string"
|
||||
},
|
||||
"providerCode": {
|
||||
"type": "string",
|
||||
"enum": ["mifapay", "v5pay", "usdt"]
|
||||
},
|
||||
"externalOrderNo": {
|
||||
"type": "string"
|
||||
},
|
||||
"chain": {
|
||||
"type": "string",
|
||||
"enum": ["TRON", "BSC"]
|
||||
},
|
||||
"usdAmount": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"usdMinorAmount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserFinanceScope": {
|
||||
"type": "object",
|
||||
"required": ["appCode", "regionId"],
|
||||
|
||||
@ -6,6 +6,7 @@ import {
|
||||
approveFinanceApplication,
|
||||
approveFinanceWithdrawalApplication,
|
||||
createFinanceApplication,
|
||||
createFinanceCoinSellerRechargeOrder,
|
||||
exportFinanceRechargeBills,
|
||||
fetchFinanceApplicationOptions,
|
||||
fetchFinanceRechargeApps,
|
||||
@ -13,7 +14,9 @@ import {
|
||||
fetchFinanceRechargeSummaries,
|
||||
fetchFinanceSession,
|
||||
filterOperationsByPermissions,
|
||||
grantFinanceCoinSellerRechargeOrder,
|
||||
listFinanceApplications,
|
||||
listFinanceCoinSellerRechargeOrders,
|
||||
listFinanceRechargeDetails,
|
||||
listFinanceRechargeRegions,
|
||||
listMyFinanceApplications,
|
||||
@ -21,10 +24,13 @@ import {
|
||||
refreshFinanceGoogleRechargePaid,
|
||||
rejectFinanceApplication,
|
||||
rejectFinanceWithdrawalApplication,
|
||||
verifyFinanceCoinSellerRechargeReceipt,
|
||||
verifyFinanceCoinSellerRechargeOrder,
|
||||
} from "./api.js";
|
||||
import { FinanceApplicationForm } from "./components/FinanceApplicationForm.jsx";
|
||||
import { FinanceApplicationList } from "./components/FinanceApplicationList.jsx";
|
||||
import { FinanceAuditDialog } from "./components/FinanceAuditDialog.jsx";
|
||||
import { FinanceCoinSellerRechargeOrderList } from "./components/FinanceCoinSellerRechargeOrderList.jsx";
|
||||
import { FinanceMyApplicationList } from "./components/FinanceMyApplicationList.jsx";
|
||||
import { FinanceOverview } from "./components/FinanceOverview.jsx";
|
||||
import { FinanceReconciliation } from "./components/FinanceReconciliation.jsx";
|
||||
@ -124,6 +130,12 @@ export function FinanceApp() {
|
||||
error: "",
|
||||
loading: false,
|
||||
});
|
||||
const [coinSellerRechargeOrdersState, setCoinSellerRechargeOrdersState] = useState({
|
||||
data: { items: [], page: 1, pageSize, total: 0 },
|
||||
error: "",
|
||||
loading: false,
|
||||
});
|
||||
const [coinSellerRechargeOrderActionLoading, setCoinSellerRechargeOrderActionLoading] = useState("");
|
||||
const [activeView, setActiveView] = useState("create");
|
||||
const [rechargeDetailFilters, setRechargeDetailFilters] = useState(() => ({
|
||||
appCode: "",
|
||||
@ -139,6 +151,15 @@ export function FinanceApp() {
|
||||
const [myFilters, setMyFilters] = useState({ appCode: "", keyword: "", operation: "", page: 1, status: "" });
|
||||
const [filters, setFilters] = useState({ appCode: "", keyword: "", operation: "", page: 1, status: "pending" });
|
||||
const [withdrawalFilters, setWithdrawalFilters] = useState({ appCode: "", keyword: "", page: 1 });
|
||||
const [coinSellerRechargeOrderFilters, setCoinSellerRechargeOrderFilters] = useState({
|
||||
appCode: "",
|
||||
grantStatus: "",
|
||||
keyword: "",
|
||||
page: 1,
|
||||
providerCode: "",
|
||||
status: "",
|
||||
verifyStatus: "",
|
||||
});
|
||||
const [form, setForm] = useState(DEFAULT_APPLICATION_FORM);
|
||||
const [createDialogOpen, setCreateDialogOpen] = useState(false);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
@ -158,11 +179,13 @@ export function FinanceApp() {
|
||||
setActiveView(
|
||||
session.canViewAppRechargeDetails
|
||||
? "overview"
|
||||
: session.canAuditApplication
|
||||
? "applications"
|
||||
: session.canCreateApplication
|
||||
? "create"
|
||||
: "withdrawals",
|
||||
: session.canViewCoinSellerRechargeOrders
|
||||
? "coinSellerRechargeOrders"
|
||||
: session.canAuditApplication
|
||||
? "applications"
|
||||
: session.canCreateApplication
|
||||
? "create"
|
||||
: "withdrawals",
|
||||
);
|
||||
})
|
||||
.catch((err) => {
|
||||
@ -233,6 +256,19 @@ export function FinanceApp() {
|
||||
}),
|
||||
[withdrawalFilters],
|
||||
);
|
||||
const coinSellerRechargeOrderQuery = useMemo(
|
||||
() => ({
|
||||
app_code: coinSellerRechargeOrderFilters.appCode,
|
||||
grant_status: coinSellerRechargeOrderFilters.grantStatus,
|
||||
keyword: coinSellerRechargeOrderFilters.keyword,
|
||||
page: coinSellerRechargeOrderFilters.page,
|
||||
page_size: pageSize,
|
||||
provider_code: coinSellerRechargeOrderFilters.providerCode,
|
||||
status: coinSellerRechargeOrderFilters.status,
|
||||
verify_status: coinSellerRechargeOrderFilters.verifyStatus,
|
||||
}),
|
||||
[coinSellerRechargeOrderFilters],
|
||||
);
|
||||
|
||||
const loadOptions = useCallback(async () => {
|
||||
if (!canLoadOptions) {
|
||||
@ -464,6 +500,23 @@ export function FinanceApp() {
|
||||
}
|
||||
}, [session?.canViewWithdrawalApplications, withdrawalQuery]);
|
||||
|
||||
const loadCoinSellerRechargeOrders = useCallback(async () => {
|
||||
if (!session?.canViewCoinSellerRechargeOrders) {
|
||||
return;
|
||||
}
|
||||
setCoinSellerRechargeOrdersState((current) => ({ ...current, error: "", loading: true }));
|
||||
try {
|
||||
const data = await listFinanceCoinSellerRechargeOrders(coinSellerRechargeOrderQuery);
|
||||
setCoinSellerRechargeOrdersState({ data, error: "", loading: false });
|
||||
} catch (err) {
|
||||
setCoinSellerRechargeOrdersState((current) => ({
|
||||
...current,
|
||||
error: err.message || "加载币商充值订单失败",
|
||||
loading: false,
|
||||
}));
|
||||
}
|
||||
}, [coinSellerRechargeOrderQuery, session?.canViewCoinSellerRechargeOrders]);
|
||||
|
||||
useEffect(() => {
|
||||
loadOptions();
|
||||
}, [loadOptions]);
|
||||
@ -504,6 +557,10 @@ export function FinanceApp() {
|
||||
loadWithdrawals();
|
||||
}, [loadWithdrawals]);
|
||||
|
||||
useEffect(() => {
|
||||
loadCoinSellerRechargeOrders();
|
||||
}, [loadCoinSellerRechargeOrders]);
|
||||
|
||||
const operationOptions = useMemo(
|
||||
() => filterOperationsByPermissions(optionsState.data.operations, session?.permissions || []),
|
||||
[optionsState.data.operations, session?.permissions],
|
||||
@ -580,6 +637,72 @@ export function FinanceApp() {
|
||||
}
|
||||
};
|
||||
|
||||
const submitCoinSellerRechargeOrder = async (payload) => {
|
||||
if (!session?.canCreateCoinSellerRechargeOrder) {
|
||||
showToast("无创建币商充值订单权限", "error");
|
||||
return null;
|
||||
}
|
||||
setCoinSellerRechargeOrderActionLoading("create");
|
||||
try {
|
||||
const order = await createFinanceCoinSellerRechargeOrder(cleanPayload(payload));
|
||||
showToast("币商充值订单已创建", "success");
|
||||
await loadCoinSellerRechargeOrders();
|
||||
return order;
|
||||
} catch (err) {
|
||||
showToast(err.message || "创建币商充值订单失败", "error");
|
||||
return null;
|
||||
} finally {
|
||||
setCoinSellerRechargeOrderActionLoading("");
|
||||
}
|
||||
};
|
||||
|
||||
const verifyCoinSellerRechargeReceipt = async (payload) => {
|
||||
if (!session?.canVerifyCoinSellerRechargeOrder) {
|
||||
showToast("无校验币商充值订单权限", "error");
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const verification = await verifyFinanceCoinSellerRechargeReceipt(cleanPayload(payload));
|
||||
showToast(verification?.verified ? "订单号校验通过" : verification?.failureReason || "订单号校验未通过", verification?.verified ? "success" : "error");
|
||||
return verification;
|
||||
} catch (err) {
|
||||
showToast(err.message || "校验订单号失败", "error");
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const verifyCoinSellerRechargeOrder = async (order) => {
|
||||
if (!order?.id || !session?.canVerifyCoinSellerRechargeOrder) {
|
||||
return;
|
||||
}
|
||||
setCoinSellerRechargeOrderActionLoading(`verify:${order.id}`);
|
||||
try {
|
||||
await verifyFinanceCoinSellerRechargeOrder(order.id);
|
||||
showToast("订单校验已完成", "success");
|
||||
await loadCoinSellerRechargeOrders();
|
||||
} catch (err) {
|
||||
showToast(err.message || "校验币商充值订单失败", "error");
|
||||
} finally {
|
||||
setCoinSellerRechargeOrderActionLoading("");
|
||||
}
|
||||
};
|
||||
|
||||
const grantCoinSellerRechargeOrder = async (order) => {
|
||||
if (!order?.id || !session?.canGrantCoinSellerRechargeOrder) {
|
||||
return;
|
||||
}
|
||||
setCoinSellerRechargeOrderActionLoading(`grant:${order.id}`);
|
||||
try {
|
||||
await grantFinanceCoinSellerRechargeOrder(order.id);
|
||||
showToast("币商充值已发放", "success");
|
||||
await loadCoinSellerRechargeOrders();
|
||||
} catch (err) {
|
||||
showToast(err.message || "发放币商充值失败", "error");
|
||||
} finally {
|
||||
setCoinSellerRechargeOrderActionLoading("");
|
||||
}
|
||||
};
|
||||
|
||||
const updateFilters = (nextFilters) => {
|
||||
setFilters((current) => ({ ...current, ...nextFilters, page: nextFilters.page || 1 }));
|
||||
};
|
||||
@ -596,6 +719,10 @@ export function FinanceApp() {
|
||||
setWithdrawalFilters((current) => ({ ...current, ...nextFilters, page: nextFilters.page || 1 }));
|
||||
};
|
||||
|
||||
const updateCoinSellerRechargeOrderFilters = (nextFilters) => {
|
||||
setCoinSellerRechargeOrderFilters((current) => ({ ...current, ...nextFilters, page: nextFilters.page || 1 }));
|
||||
};
|
||||
|
||||
const closeCreateDialog = (_, reason) => {
|
||||
if (submitting || reason === "backdropClick") {
|
||||
return;
|
||||
@ -695,6 +822,7 @@ export function FinanceApp() {
|
||||
appBar={appBar}
|
||||
canAudit={session.canAuditApplication}
|
||||
canCreate={session.canCreateApplication}
|
||||
canViewCoinSellerRechargeOrders={session.canViewCoinSellerRechargeOrders}
|
||||
canViewRechargeDetails={session.canViewAppRechargeDetails}
|
||||
canViewWithdrawals={session.canViewWithdrawalApplications}
|
||||
loading={
|
||||
@ -702,7 +830,8 @@ export function FinanceApp() {
|
||||
rechargeDetailsState.loading ||
|
||||
myApplicationsState.loading ||
|
||||
applicationsState.loading ||
|
||||
withdrawalsState.loading
|
||||
withdrawalsState.loading ||
|
||||
coinSellerRechargeOrdersState.loading
|
||||
}
|
||||
session={session}
|
||||
toolbar={financeToolbar}
|
||||
@ -730,6 +859,24 @@ export function FinanceApp() {
|
||||
}}
|
||||
onOpenWithdrawals={() => setActiveView("withdrawals")}
|
||||
/>
|
||||
) : activeView === "coinSellerRechargeOrders" && session.canViewCoinSellerRechargeOrders ? (
|
||||
<FinanceCoinSellerRechargeOrderList
|
||||
actionLoading={coinSellerRechargeOrderActionLoading}
|
||||
apps={rechargeApps}
|
||||
canCreate={session.canCreateCoinSellerRechargeOrder}
|
||||
canGrant={session.canGrantCoinSellerRechargeOrder}
|
||||
canVerify={session.canVerifyCoinSellerRechargeOrder}
|
||||
error={coinSellerRechargeOrdersState.error}
|
||||
filters={coinSellerRechargeOrderFilters}
|
||||
loading={coinSellerRechargeOrdersState.loading}
|
||||
orders={coinSellerRechargeOrdersState.data}
|
||||
onCreate={submitCoinSellerRechargeOrder}
|
||||
onFiltersChange={updateCoinSellerRechargeOrderFilters}
|
||||
onGrant={grantCoinSellerRechargeOrder}
|
||||
onReload={loadCoinSellerRechargeOrders}
|
||||
onVerify={verifyCoinSellerRechargeOrder}
|
||||
onVerifyReceipt={verifyCoinSellerRechargeReceipt}
|
||||
/>
|
||||
) : activeView === "recon" && session.canViewAppRechargeDetails ? (
|
||||
<FinanceReconciliation
|
||||
loading={reconState.loading}
|
||||
|
||||
@ -7,6 +7,7 @@ vi.mock("./api.js", () => ({
|
||||
approveFinanceApplication: vi.fn(),
|
||||
approveFinanceWithdrawalApplication: vi.fn(),
|
||||
createFinanceApplication: vi.fn(),
|
||||
createFinanceCoinSellerRechargeOrder: vi.fn(),
|
||||
fetchFinanceApplicationOptions: vi.fn(async () => ({
|
||||
apps: [{ appCode: "lalu", appName: "lalu" }],
|
||||
operations: [{ label: "增加用户金币", permissionCode: "finance-operation:user-coin-credit", value: "user_coin_credit" }],
|
||||
@ -16,18 +17,25 @@ vi.mock("./api.js", () => ({
|
||||
canAccessFinance: true,
|
||||
canAuditApplication: false,
|
||||
canCreateApplication: true,
|
||||
canCreateCoinSellerRechargeOrder: false,
|
||||
canGrantCoinSellerRechargeOrder: false,
|
||||
canVerifyCoinSellerRechargeOrder: false,
|
||||
canViewAppRechargeDetails: false,
|
||||
canViewCoinSellerRechargeOrders: false,
|
||||
canViewWithdrawalApplications: false,
|
||||
permissions: ["finance-application:create", "finance-operation:user-coin-credit"],
|
||||
user: { account: "admin", name: "admin" }
|
||||
})),
|
||||
filterOperationsByPermissions: vi.fn((operations, permissions) => operations.filter((item) => permissions.includes(item.permissionCode))),
|
||||
grantFinanceCoinSellerRechargeOrder: vi.fn(),
|
||||
listFinanceApplications: vi.fn(),
|
||||
listFinanceCoinSellerRechargeOrders: vi.fn(async () => ({ items: [], page: 1, pageSize: 50, total: 0 })),
|
||||
listFinanceRechargeDetails: vi.fn(),
|
||||
listFinanceWithdrawalApplications: vi.fn(),
|
||||
listMyFinanceApplications: vi.fn(async () => ({ items: [], page: 1, pageSize: 50, total: 0 })),
|
||||
rejectFinanceApplication: vi.fn(),
|
||||
rejectFinanceWithdrawalApplication: vi.fn()
|
||||
rejectFinanceWithdrawalApplication: vi.fn(),
|
||||
verifyFinanceCoinSellerRechargeOrder: vi.fn()
|
||||
}));
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@ -6,6 +6,8 @@ import {
|
||||
mergeRechargeOverviews,
|
||||
mergeRechargeSummaries,
|
||||
normalizeApplicationPage,
|
||||
normalizeCoinSellerRechargeOrder,
|
||||
normalizeCoinSellerRechargeOrderPage,
|
||||
normalizeRechargeBillPage,
|
||||
normalizeRechargeOverview,
|
||||
normalizeRechargeRegions,
|
||||
@ -24,14 +26,27 @@ export async function fetchFinanceSession() {
|
||||
permissions.includes(FINANCE_PERMISSIONS.auditWithdrawalApplications) || permissions.includes(FINANCE_PERMISSIONS.auditApplication);
|
||||
const canViewWithdrawalApplications = permissions.includes(FINANCE_PERMISSIONS.viewWithdrawalApplications) || canAuditWithdrawalApplications;
|
||||
const canViewAppRechargeDetails = permissions.includes(FINANCE_PERMISSIONS.viewAppRechargeDetails);
|
||||
const canViewCoinSellerRechargeOrders = permissions.includes(FINANCE_PERMISSIONS.viewCoinSellerRechargeOrders);
|
||||
const canCreateCoinSellerRechargeOrder = permissions.includes(FINANCE_PERMISSIONS.createCoinSellerRechargeOrder);
|
||||
const canVerifyCoinSellerRechargeOrder = permissions.includes(FINANCE_PERMISSIONS.verifyCoinSellerRechargeOrder);
|
||||
const canGrantCoinSellerRechargeOrder = permissions.includes(FINANCE_PERMISSIONS.grantCoinSellerRechargeOrder);
|
||||
|
||||
return {
|
||||
...session,
|
||||
canAccessFinance: canCreateApplication || canAuditApplication || canViewWithdrawalApplications || canViewAppRechargeDetails,
|
||||
canAccessFinance:
|
||||
canCreateApplication ||
|
||||
canAuditApplication ||
|
||||
canViewWithdrawalApplications ||
|
||||
canViewAppRechargeDetails ||
|
||||
canViewCoinSellerRechargeOrders,
|
||||
canAuditApplication,
|
||||
canAuditWithdrawalApplications,
|
||||
canCreateApplication,
|
||||
canCreateCoinSellerRechargeOrder,
|
||||
canGrantCoinSellerRechargeOrder,
|
||||
canVerifyCoinSellerRechargeOrder,
|
||||
canViewAppRechargeDetails,
|
||||
canViewCoinSellerRechargeOrders,
|
||||
canViewWithdrawalApplications,
|
||||
permissions
|
||||
};
|
||||
@ -81,6 +96,48 @@ export async function listFinanceWithdrawalApplications(query = {}) {
|
||||
return normalizeWithdrawalApplicationPage(data);
|
||||
}
|
||||
|
||||
export async function listFinanceCoinSellerRechargeOrders(query = {}) {
|
||||
const endpoint = API_ENDPOINTS.listFinanceCoinSellerRechargeOrders;
|
||||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.listFinanceCoinSellerRechargeOrders), {
|
||||
method: endpoint.method,
|
||||
query
|
||||
});
|
||||
return normalizeCoinSellerRechargeOrderPage(data);
|
||||
}
|
||||
|
||||
export async function createFinanceCoinSellerRechargeOrder(payload) {
|
||||
const endpoint = API_ENDPOINTS.createFinanceCoinSellerRechargeOrder;
|
||||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.createFinanceCoinSellerRechargeOrder), {
|
||||
body: payload,
|
||||
method: endpoint.method
|
||||
});
|
||||
return normalizeCoinSellerRechargeOrder(data);
|
||||
}
|
||||
|
||||
export async function verifyFinanceCoinSellerRechargeReceipt(payload) {
|
||||
const endpoint = API_ENDPOINTS.verifyFinanceCoinSellerRechargeReceipt;
|
||||
return apiRequest(apiEndpointPath(API_OPERATIONS.verifyFinanceCoinSellerRechargeReceipt), {
|
||||
body: cleanPayload(payload),
|
||||
method: endpoint.method
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyFinanceCoinSellerRechargeOrder(orderId) {
|
||||
const endpoint = API_ENDPOINTS.verifyFinanceCoinSellerRechargeOrder;
|
||||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.verifyFinanceCoinSellerRechargeOrder, { order_id: orderId }), {
|
||||
method: endpoint.method
|
||||
});
|
||||
return normalizeCoinSellerRechargeOrder(data);
|
||||
}
|
||||
|
||||
export async function grantFinanceCoinSellerRechargeOrder(orderId) {
|
||||
const endpoint = API_ENDPOINTS.grantFinanceCoinSellerRechargeOrder;
|
||||
const data = await apiRequest(apiEndpointPath(API_OPERATIONS.grantFinanceCoinSellerRechargeOrder, { order_id: orderId }), {
|
||||
method: endpoint.method
|
||||
});
|
||||
return normalizeCoinSellerRechargeOrder(data);
|
||||
}
|
||||
|
||||
export async function listFinanceRechargeDetails(query = {}, apps = []) {
|
||||
const page = positiveInteger(query.page, 1);
|
||||
const pageSize = positiveInteger(query.page_size ?? query.pageSize, 50);
|
||||
@ -326,6 +383,12 @@ function cleanRechargeBillQuery(query = {}) {
|
||||
return rest;
|
||||
}
|
||||
|
||||
function cleanPayload(payload = {}) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(payload).filter(([, value]) => value !== undefined && value !== null && value !== "")
|
||||
);
|
||||
}
|
||||
|
||||
function positiveInteger(value, fallback) {
|
||||
const number = Number(value);
|
||||
return Number.isInteger(number) && number > 0 ? number : fallback;
|
||||
|
||||
@ -4,14 +4,19 @@ import {
|
||||
approveFinanceApplication,
|
||||
approveFinanceWithdrawalApplication,
|
||||
createFinanceApplication,
|
||||
createFinanceCoinSellerRechargeOrder,
|
||||
fetchFinanceApplicationOptions,
|
||||
filterOperationsByPermissions,
|
||||
grantFinanceCoinSellerRechargeOrder,
|
||||
listFinanceApplications,
|
||||
listFinanceCoinSellerRechargeOrders,
|
||||
listFinanceRechargeDetails,
|
||||
listMyFinanceApplications,
|
||||
listFinanceWithdrawalApplications,
|
||||
rejectFinanceApplication,
|
||||
rejectFinanceWithdrawalApplication
|
||||
rejectFinanceWithdrawalApplication,
|
||||
verifyFinanceCoinSellerRechargeReceipt,
|
||||
verifyFinanceCoinSellerRechargeOrder
|
||||
} from "./api.js";
|
||||
import { FINANCE_OPERATIONS } from "./constants.js";
|
||||
|
||||
@ -92,6 +97,56 @@ test("finance application APIs use generated endpoint paths", async () => {
|
||||
expect(JSON.parse(String(calls[8][1]?.body))).toEqual({ auditRemark: "bad" });
|
||||
});
|
||||
|
||||
test("finance coin seller recharge order APIs use generated endpoint paths", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async (url) => {
|
||||
if (String(url).includes("/coin-seller-recharges?")) {
|
||||
return new Response(JSON.stringify({ code: 0, data: { items: [], page: 1, pageSize: 50, total: 0 } }));
|
||||
}
|
||||
return new Response(JSON.stringify({ code: 0, data: { id: "order-1", targetUserId: "10001" } }));
|
||||
})
|
||||
);
|
||||
|
||||
await listFinanceCoinSellerRechargeOrders({ page: 1, page_size: 50, provider_code: "mifapay" });
|
||||
await createFinanceCoinSellerRechargeOrder({
|
||||
appCode: "lalu",
|
||||
coinAmount: 100000,
|
||||
externalOrderNo: "MIFA-001",
|
||||
providerCode: "mifapay",
|
||||
targetUserId: "10001",
|
||||
usdAmount: 10
|
||||
});
|
||||
await verifyFinanceCoinSellerRechargeReceipt({
|
||||
appCode: "lalu",
|
||||
externalOrderNo: "MIFA-001",
|
||||
providerCode: "mifapay",
|
||||
usdAmount: 10
|
||||
});
|
||||
await verifyFinanceCoinSellerRechargeOrder("order-1");
|
||||
await grantFinanceCoinSellerRechargeOrder("order-1");
|
||||
|
||||
const calls = vi.mocked(fetch).mock.calls;
|
||||
expect(String(calls[0][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges?");
|
||||
expect(String(calls[0][0])).toContain("provider_code=mifapay");
|
||||
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(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",
|
||||
providerCode: "mifapay",
|
||||
usdAmount: 10
|
||||
});
|
||||
expect(String(calls[3][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/verify");
|
||||
expect(calls[3][1]?.method).toBe("POST");
|
||||
expect(String(calls[4][0])).toContain("/api/v1/admin/finance/orders/coin-seller-recharges/order-1/grant");
|
||||
expect(calls[4][1]?.method).toBe("POST");
|
||||
});
|
||||
|
||||
test("filters operation options by backend-configured permissions", () => {
|
||||
const allowed = filterOperationsByPermissions(FINANCE_OPERATIONS, [
|
||||
"finance-operation:user-coin-credit",
|
||||
|
||||
539
finance/src/components/FinanceCoinSellerRechargeOrderList.jsx
Normal file
539
finance/src/components/FinanceCoinSellerRechargeOrderList.jsx
Normal file
@ -0,0 +1,539 @@
|
||||
import AddOutlined from "@mui/icons-material/AddOutlined";
|
||||
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 Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import MenuItem from "@mui/material/MenuItem";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
COIN_SELLER_RECHARGE_CHAINS,
|
||||
COIN_SELLER_RECHARGE_PROVIDERS,
|
||||
COIN_SELLER_RECHARGE_STATUS,
|
||||
DEFAULT_COIN_SELLER_RECHARGE_ORDER_FORM,
|
||||
} from "../constants.js";
|
||||
import {
|
||||
buildCoinSellerRechargeReceiptPayload,
|
||||
buildCoinSellerRechargeOrderPayload,
|
||||
coinSellerRechargeChainLabel,
|
||||
coinSellerRechargeGrantStatusLabel,
|
||||
coinSellerRechargeProviderLabel,
|
||||
coinSellerRechargeStatusLabel,
|
||||
coinSellerRechargeStatusTone,
|
||||
coinSellerRechargeVerifyStatusLabel,
|
||||
formatAmount,
|
||||
formatTime,
|
||||
formatUsdMinor,
|
||||
isCoinSellerRechargeGranted,
|
||||
isCoinSellerRechargeVerified,
|
||||
validateCoinSellerRechargeOrderPayload,
|
||||
} from "../format.js";
|
||||
|
||||
export function FinanceCoinSellerRechargeOrderList({
|
||||
actionLoading,
|
||||
apps,
|
||||
canCreate,
|
||||
canGrant,
|
||||
canVerify,
|
||||
error,
|
||||
filters,
|
||||
loading,
|
||||
onCreate,
|
||||
onFiltersChange,
|
||||
onGrant,
|
||||
onReload,
|
||||
onVerify,
|
||||
onVerifyReceipt,
|
||||
orders,
|
||||
}) {
|
||||
const [createOpen, setCreateOpen] = useState(false);
|
||||
const [form, setForm] = useState(DEFAULT_COIN_SELLER_RECHARGE_ORDER_FORM);
|
||||
const [formError, setFormError] = useState("");
|
||||
const [receiptVerification, setReceiptVerification] = useState(null);
|
||||
const [receiptLoading, setReceiptLoading] = useState(false);
|
||||
const items = orders.items || [];
|
||||
const page = Number(orders.page || filters.page || 1);
|
||||
const pageSize = Number(orders.pageSize || 50);
|
||||
const total = Number(orders.total || 0);
|
||||
const hasNextPage = page * pageSize < total;
|
||||
|
||||
const openCreate = () => {
|
||||
setForm({
|
||||
...DEFAULT_COIN_SELLER_RECHARGE_ORDER_FORM,
|
||||
appCode: filters.appCode || apps[0]?.appCode || "",
|
||||
});
|
||||
setFormError("");
|
||||
setReceiptVerification(null);
|
||||
setCreateOpen(true);
|
||||
};
|
||||
|
||||
const updateForm = (key, value) => {
|
||||
if (key === "providerCode") {
|
||||
setForm((current) => ({
|
||||
...current,
|
||||
chain: "",
|
||||
providerCode: value,
|
||||
}));
|
||||
setFormError("");
|
||||
setReceiptVerification(null);
|
||||
return;
|
||||
}
|
||||
setForm((current) => ({ ...current, [key]: value }));
|
||||
setFormError("");
|
||||
// 订单号校验结果绑定当前 APP、用户输入金额、渠道、链和订单号;任一字段变化都不能复用旧凭证。
|
||||
setReceiptVerification(null);
|
||||
};
|
||||
|
||||
const submitCreate = async (event) => {
|
||||
event.preventDefault();
|
||||
if (!receiptVerification?.verified) {
|
||||
setFormError("请先校验订单号");
|
||||
return;
|
||||
}
|
||||
const payload = buildCoinSellerRechargeOrderPayload(form);
|
||||
const validationMessage = validateCoinSellerRechargeOrderPayload(payload);
|
||||
if (validationMessage) {
|
||||
setFormError(validationMessage);
|
||||
return;
|
||||
}
|
||||
const created = await onCreate(payload);
|
||||
if (created) {
|
||||
setCreateOpen(false);
|
||||
setReceiptVerification(null);
|
||||
}
|
||||
};
|
||||
|
||||
const verifyCreateReceipt = async () => {
|
||||
const payload = buildCoinSellerRechargeReceiptPayload(form);
|
||||
const validationMessage = validateCoinSellerRechargeOrderPayload({
|
||||
...buildCoinSellerRechargeOrderPayload(form),
|
||||
coinAmount: 1,
|
||||
targetUserId: "verification-only",
|
||||
});
|
||||
if (validationMessage) {
|
||||
setFormError(validationMessage);
|
||||
return;
|
||||
}
|
||||
setFormError("");
|
||||
setReceiptLoading(true);
|
||||
try {
|
||||
const verification = await onVerifyReceipt(payload);
|
||||
setReceiptVerification(verification);
|
||||
if (!verification?.verified) {
|
||||
setFormError(verification?.failureReason || "订单号校验未通过");
|
||||
}
|
||||
} finally {
|
||||
setReceiptLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const confirmGrant = (order) => {
|
||||
const userText = order.targetDisplayUserId || order.targetUserId || "-";
|
||||
const ok = window.confirm(
|
||||
isZeroCoinMakeupOrder(order)
|
||||
? `确认完成用户 ${userText} 的补单?该订单计入充值,不发放金币。`
|
||||
: `确认给用户 ${userText} 发放 ${formatAmount(order.coinAmount)} 金币?`,
|
||||
);
|
||||
if (ok) {
|
||||
onGrant(order);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="finance-card finance-list">
|
||||
<div className="finance-filterrow">
|
||||
<TextField
|
||||
className="finance-filterrow__region"
|
||||
label="APP"
|
||||
select
|
||||
size="small"
|
||||
value={filters.appCode}
|
||||
onChange={(event) => onFiltersChange({ appCode: event.target.value })}
|
||||
>
|
||||
<MenuItem value="">全部 APP</MenuItem>
|
||||
{apps.map((app) => (
|
||||
<MenuItem key={app.appCode} value={app.appCode}>
|
||||
{app.appName || app.appCode}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
label="渠道"
|
||||
select
|
||||
size="small"
|
||||
value={filters.providerCode}
|
||||
onChange={(event) => onFiltersChange({ providerCode: event.target.value })}
|
||||
>
|
||||
<MenuItem value="">全部渠道</MenuItem>
|
||||
{COIN_SELLER_RECHARGE_PROVIDERS.map(([value, label]) => (
|
||||
<MenuItem key={value} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
label="订单状态"
|
||||
select
|
||||
size="small"
|
||||
value={filters.status}
|
||||
onChange={(event) => onFiltersChange({ status: event.target.value })}
|
||||
>
|
||||
<MenuItem value="">全部状态</MenuItem>
|
||||
{COIN_SELLER_RECHARGE_STATUS.map(([value, label]) => (
|
||||
<MenuItem key={value} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<TextField
|
||||
label="搜索"
|
||||
size="small"
|
||||
value={filters.keyword}
|
||||
onChange={(event) => onFiltersChange({ keyword: event.target.value })}
|
||||
/>
|
||||
<span className="finance-filterrow__spacer" />
|
||||
<Button startIcon={<RefreshOutlined fontSize="small" />} variant="outlined" onClick={onReload}>
|
||||
刷新
|
||||
</Button>
|
||||
<Button disabled={!canCreate} startIcon={<AddOutlined fontSize="small" />} variant="contained" onClick={openCreate}>
|
||||
创建币商充值
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error ? <div className="finance-error">{error}</div> : null}
|
||||
{loading && !items.length ? <div className="finance-skeleton" /> : null}
|
||||
{!loading || items.length ? (
|
||||
<div className="finance-tablewrap">
|
||||
<table className="finance-flat-table finance-flat-table--orders">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单</th>
|
||||
<th>APP</th>
|
||||
<th>目标用户</th>
|
||||
<th className="r">金额</th>
|
||||
<th>渠道</th>
|
||||
<th>三方订单</th>
|
||||
<th>备注</th>
|
||||
<th>状态</th>
|
||||
<th>钱包</th>
|
||||
<th>操作人</th>
|
||||
<th>时间</th>
|
||||
<th className="r">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.length ? (
|
||||
items.map((item) => (
|
||||
<tr className="finance-flat-table__row" key={item.id}>
|
||||
<td className="finance-order-cell">{item.id || "-"}</td>
|
||||
<td>{appName(item.appCode, apps)}</td>
|
||||
<td>
|
||||
<Stacked primary={item.targetDisplayUserId || item.targetUserId} secondary={item.targetUserId} />
|
||||
</td>
|
||||
<td className="r num">
|
||||
<Stacked
|
||||
primary={usdText(item)}
|
||||
secondary={`${formatAmount(item.coinAmount)} 金币${isZeroCoinMakeupOrder(item) ? "(补单)" : ""}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Stacked primary={coinSellerRechargeProviderLabel(item.providerCode)} secondary={providerMeta(item)} />
|
||||
</td>
|
||||
<td>
|
||||
<Stacked primary={item.externalOrderNo || "-"} secondary={providerAmountText(item)} />
|
||||
</td>
|
||||
<td className="finance-remark-cell">{item.remark || "-"}</td>
|
||||
<td>
|
||||
<Stacked
|
||||
primary={<StatusBadge status={item.status} />}
|
||||
secondary={`${coinSellerRechargeVerifyStatusLabel(item.verifyStatus)} / ${coinSellerRechargeGrantStatusLabel(item.grantStatus)}`}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Stacked
|
||||
primary={item.walletTransactionId || item.walletCommandId || "-"}
|
||||
secondary={walletMeta(item)}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Stacked
|
||||
primary={item.operatorName || "-"}
|
||||
secondary={[
|
||||
item.verifiedByName && `校验:${item.verifiedByName}`,
|
||||
item.grantedByName && `${isZeroCoinMakeupOrder(item) ? "补单" : "发放"}:${item.grantedByName}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" / ")}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Stacked
|
||||
primary={formatTime(item.createdAtMs)}
|
||||
secondary={[
|
||||
item.verifiedAtMs && `校验 ${formatTime(item.verifiedAtMs)}`,
|
||||
item.grantedAtMs && `${isZeroCoinMakeupOrder(item) ? "补单" : "发放"} ${formatTime(item.grantedAtMs)}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" / ")}
|
||||
/>
|
||||
</td>
|
||||
<td className="r">
|
||||
<span className="finance-row-actions">
|
||||
<Button
|
||||
disabled={
|
||||
!canVerify ||
|
||||
actionLoading === `verify:${item.id}` ||
|
||||
isCoinSellerRechargeVerified(item) ||
|
||||
isCoinSellerRechargeGranted(item)
|
||||
}
|
||||
size="small"
|
||||
startIcon={<FactCheckOutlined fontSize="small" />}
|
||||
variant="outlined"
|
||||
onClick={() => onVerify(item)}
|
||||
>
|
||||
校验
|
||||
</Button>
|
||||
<Button
|
||||
disabled={
|
||||
!canGrant ||
|
||||
actionLoading === `grant:${item.id}` ||
|
||||
!isCoinSellerRechargeVerified(item) ||
|
||||
isCoinSellerRechargeGranted(item)
|
||||
}
|
||||
size="small"
|
||||
startIcon={<PaidOutlined fontSize="small" />}
|
||||
variant="contained"
|
||||
onClick={() => confirmGrant(item)}
|
||||
>
|
||||
{isZeroCoinMakeupOrder(item) ? "完成补单" : "发放"}
|
||||
</Button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={12} style={{ textAlign: "center" }}>
|
||||
当前无数据
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : null}
|
||||
{total > pageSize ? (
|
||||
<div className="finance-pagination">
|
||||
<Button disabled={page <= 1 || loading} variant="outlined" onClick={() => onFiltersChange({ page: page - 1 })}>
|
||||
上一页
|
||||
</Button>
|
||||
<span>
|
||||
第 {page} 页 / 共 {total} 条
|
||||
</span>
|
||||
<Button disabled={!hasNextPage || loading} variant="outlined" onClick={() => onFiltersChange({ page: page + 1 })}>
|
||||
下一页
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
<CreateDialog
|
||||
apps={apps}
|
||||
form={form}
|
||||
formError={formError}
|
||||
loading={actionLoading === "create"}
|
||||
open={createOpen}
|
||||
receiptLoading={receiptLoading}
|
||||
receiptVerification={receiptVerification}
|
||||
onChange={updateForm}
|
||||
onClose={() => {
|
||||
setCreateOpen(false);
|
||||
setReceiptVerification(null);
|
||||
setFormError("");
|
||||
}}
|
||||
onSubmit={submitCreate}
|
||||
onVerifyReceipt={verifyCreateReceipt}
|
||||
/>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateDialog({
|
||||
apps,
|
||||
form,
|
||||
formError,
|
||||
loading,
|
||||
onChange,
|
||||
onClose,
|
||||
onSubmit,
|
||||
onVerifyReceipt,
|
||||
open,
|
||||
receiptLoading,
|
||||
receiptVerification,
|
||||
}) {
|
||||
const disabled = loading || receiptLoading;
|
||||
const canCreate = receiptVerification?.verified === true && !disabled;
|
||||
|
||||
return (
|
||||
<Dialog fullWidth maxWidth={false} open={open} slotProps={{ paper: { className: "finance-create-dialog-paper finance-create-dialog-paper--coin-seller" } }} onClose={disabled ? undefined : onClose}>
|
||||
<DialogTitle>创建币商充值</DialogTitle>
|
||||
<DialogContent className="finance-create-dialog">
|
||||
<form className="finance-form finance-form--dialog" id="coin-seller-recharge-order-form" onSubmit={onSubmit}>
|
||||
{formError ? <Alert severity="error">{formError}</Alert> : null}
|
||||
<div className={form.providerCode === "usdt" ? "finance-form-grid finance-form-grid--coin-seller-create" : "finance-form-grid finance-form-grid--coin-seller-create finance-form-grid--coin-seller-no-chain"}>
|
||||
{apps.length ? (
|
||||
<TextField disabled={disabled} label="APP" required select value={form.appCode} onChange={(event) => onChange("appCode", event.target.value)}>
|
||||
{apps.map((app) => (
|
||||
<MenuItem key={app.appCode} value={app.appCode}>
|
||||
{app.appName || app.appCode}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
) : (
|
||||
<TextField disabled={disabled} label="APP" required value={form.appCode} onChange={(event) => onChange("appCode", event.target.value)} />
|
||||
)}
|
||||
<TextField disabled={disabled} label="目标用户 ID" required value={form.targetUserId} onChange={(event) => onChange("targetUserId", event.target.value)} />
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
InputProps={{ startAdornment: <InputAdornment position="start">USD</InputAdornment> }}
|
||||
inputProps={{ min: 0, step: "0.01" }}
|
||||
label="USD 金额"
|
||||
required
|
||||
type="number"
|
||||
value={form.usdAmount}
|
||||
onChange={(event) => onChange("usdAmount", event.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
InputProps={{ endAdornment: <InputAdornment position="end">金币</InputAdornment> }}
|
||||
inputProps={{ min: 0, step: "1" }}
|
||||
label="充值金币"
|
||||
required
|
||||
type="number"
|
||||
value={form.coinAmount}
|
||||
onChange={(event) => onChange("coinAmount", event.target.value)}
|
||||
/>
|
||||
<TextField disabled={disabled} label="支付渠道" required select value={form.providerCode} onChange={(event) => onChange("providerCode", event.target.value)}>
|
||||
{COIN_SELLER_RECHARGE_PROVIDERS.map(([value, label]) => (
|
||||
<MenuItem key={value} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{form.providerCode === "usdt" ? (
|
||||
<TextField disabled={disabled} label="USDT 链" required select value={form.chain} onChange={(event) => onChange("chain", event.target.value)}>
|
||||
{COIN_SELLER_RECHARGE_CHAINS.map(([value, label]) => (
|
||||
<MenuItem key={value} value={value}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
) : null}
|
||||
<div className="finance-receipt-field">
|
||||
<TextField disabled={disabled} label="三方订单号" required value={form.externalOrderNo} onChange={(event) => onChange("externalOrderNo", event.target.value)} />
|
||||
<Button disabled={disabled || !onVerifyReceipt} startIcon={<FactCheckOutlined fontSize="small" />} variant="outlined" onClick={onVerifyReceipt}>
|
||||
校验
|
||||
</Button>
|
||||
</div>
|
||||
<TextField
|
||||
disabled={disabled}
|
||||
inputProps={{ maxLength: 512 }}
|
||||
label="备注"
|
||||
value={form.remark}
|
||||
onChange={(event) => onChange("remark", event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{receiptVerification ? <ReceiptVerificationBanner verification={receiptVerification} /> : null}
|
||||
</form>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button disabled={disabled} variant="outlined" onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button disabled={!canCreate} form="coin-seller-recharge-order-form" type="submit" variant="contained">
|
||||
创建
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function isZeroCoinMakeupOrder(order) {
|
||||
return Number(order?.coinAmount || 0) === 0;
|
||||
}
|
||||
|
||||
function ReceiptVerificationBanner({ verification }) {
|
||||
const detail = [
|
||||
verification.status && `状态 ${verification.status}`,
|
||||
verification.providerOrderId && `三方单 ${verification.providerOrderId}`,
|
||||
verification.currencyCode && verification.providerAmountMinor !== undefined
|
||||
? formatUsdMinor(verification.providerAmountMinor, verification.currencyCode)
|
||||
: "",
|
||||
verification.failureReason,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" / ");
|
||||
return (
|
||||
<Alert className="finance-receipt-result" severity={verification.verified ? "success" : "error"}>
|
||||
{verification.verified ? "校验通过" : "校验未通过"}
|
||||
{detail ? `:${detail}` : ""}
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
function StatusBadge({ status }) {
|
||||
return <span className={`finance-status finance-status--${coinSellerRechargeStatusTone(status)}`}>{coinSellerRechargeStatusLabel(status)}</span>;
|
||||
}
|
||||
|
||||
function Stacked({ primary, secondary }) {
|
||||
return (
|
||||
<span className="finance-stack">
|
||||
<span>{primary || "-"}</span>
|
||||
<small>{secondary || ""}</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function appName(appCode, apps) {
|
||||
return apps.find((app) => app.appCode === appCode)?.appName || appCode || "-";
|
||||
}
|
||||
|
||||
function usdText(item) {
|
||||
if (item.usdAmount !== undefined && item.usdAmount !== null && item.usdAmount !== "") {
|
||||
return `USD ${item.usdAmount}`;
|
||||
}
|
||||
return formatUsdMinor(item.usdMinorAmount, "USD");
|
||||
}
|
||||
|
||||
function providerAmountText(item) {
|
||||
return item.providerAmountMinor === null || item.providerAmountMinor === undefined
|
||||
? "-"
|
||||
: formatUsdMinor(item.providerAmountMinor, item.providerCurrencyCode || "USD");
|
||||
}
|
||||
|
||||
function providerMeta(item) {
|
||||
if (item.providerCode === "usdt") {
|
||||
return [coinSellerRechargeChainLabel(item.chain), item.providerStatus].filter(Boolean).join(" / ") || "-";
|
||||
}
|
||||
if (item.providerCode === "mifapay") {
|
||||
return [item.providerStatus, item.providerOrderId].filter(Boolean).join(" / ") || "-";
|
||||
}
|
||||
if (item.providerCode === "v5pay") {
|
||||
return [item.providerStatus, item.providerCurrencyCode, item.providerOrderId].filter(Boolean).join(" / ") || "-";
|
||||
}
|
||||
return "-";
|
||||
}
|
||||
|
||||
function walletMeta(item) {
|
||||
return [
|
||||
item.walletAssetType,
|
||||
item.walletAmountDelta === null || item.walletAmountDelta === undefined ? "" : `变动 ${formatAmount(item.walletAmountDelta)}`,
|
||||
item.walletBalanceAfter === null || item.walletBalanceAfter === undefined ? "" : `余额 ${formatAmount(item.walletBalanceAfter)}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" / ");
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { afterEach, expect, test, vi } from "vitest";
|
||||
import { FinanceCoinSellerRechargeOrderList } from "./FinanceCoinSellerRechargeOrderList.jsx";
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
test("coin seller recharge order create dialog verifies receipt before create", async () => {
|
||||
const onCreate = vi.fn(async () => ({ id: "order-2" }));
|
||||
const onVerifyReceipt = vi.fn(async () => ({
|
||||
currencyCode: "USD",
|
||||
providerAmountMinor: 1000,
|
||||
providerOrderId: "V5-001",
|
||||
status: "paid",
|
||||
verified: true,
|
||||
}));
|
||||
render(<FinanceCoinSellerRechargeOrderList {...baseProps()} onCreate={onCreate} onVerifyReceipt={onVerifyReceipt} />);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "创建币商充值" }));
|
||||
fireEvent.mouseDown(screen.getByRole("combobox", { name: "支付渠道" }));
|
||||
fireEvent.click(screen.getByRole("option", { name: "V5Pay" }));
|
||||
|
||||
expect(screen.queryByRole("textbox", { name: "三方国家码" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("textbox", { name: "三方币种" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("textbox", { name: "支付类型" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("textbox", { name: "订单日期" })).not.toBeInTheDocument();
|
||||
|
||||
fireEvent.change(screen.getByRole("textbox", { name: "目标用户 ID" }), { target: { value: "10001" } });
|
||||
fireEvent.change(screen.getByRole("spinbutton", { name: "USD 金额" }), { target: { value: "10" } });
|
||||
fireEvent.change(screen.getByRole("spinbutton", { name: "充值金币" }), { target: { value: "0" } });
|
||||
fireEvent.change(screen.getByRole("textbox", { name: "三方订单号" }), { target: { value: "V5-001" } });
|
||||
fireEvent.change(screen.getByRole("textbox", { name: "备注" }), { target: { value: "补单,不发金币" } });
|
||||
expect(screen.getByRole("button", { name: "创建" })).toBeDisabled();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "校验" }));
|
||||
await screen.findByText(/校验通过/);
|
||||
await waitFor(() => expect(screen.getByRole("button", { name: "创建" })).not.toBeDisabled());
|
||||
expect(onVerifyReceipt).toHaveBeenCalledWith({
|
||||
appCode: "lalu",
|
||||
externalOrderNo: "V5-001",
|
||||
providerCode: "v5pay",
|
||||
usdAmount: 10,
|
||||
});
|
||||
fireEvent.click(screen.getByRole("button", { name: "创建" }));
|
||||
await waitFor(() =>
|
||||
expect(onCreate).toHaveBeenCalledWith({
|
||||
appCode: "lalu",
|
||||
coinAmount: 0,
|
||||
externalOrderNo: "V5-001",
|
||||
providerCode: "v5pay",
|
||||
remark: "补单,不发金币",
|
||||
targetUserId: "10001",
|
||||
usdAmount: 10,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test("coin seller recharge order actions follow verify and grant status", () => {
|
||||
const onVerify = vi.fn();
|
||||
const onGrant = vi.fn();
|
||||
vi.stubGlobal("confirm", vi.fn(() => true));
|
||||
|
||||
render(
|
||||
<FinanceCoinSellerRechargeOrderList
|
||||
{...baseProps({
|
||||
orders: {
|
||||
items: [
|
||||
orderFixture({ id: "pending", verifyStatus: "pending", grantStatus: "pending" }),
|
||||
orderFixture({ id: "verified", verifyStatus: "verified", grantStatus: "pending" }),
|
||||
],
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
total: 2,
|
||||
},
|
||||
})}
|
||||
onGrant={onGrant}
|
||||
onVerify={onVerify}
|
||||
/>,
|
||||
);
|
||||
|
||||
const verifyButtons = screen.getAllByRole("button", { name: "校验" });
|
||||
const grantButtons = screen.getAllByRole("button", { name: "发放" });
|
||||
fireEvent.click(verifyButtons[0]);
|
||||
fireEvent.click(grantButtons[1]);
|
||||
|
||||
expect(onVerify).toHaveBeenCalledWith(expect.objectContaining({ id: "pending" }));
|
||||
expect(onGrant).toHaveBeenCalledWith(expect.objectContaining({ id: "verified" }));
|
||||
expect(screen.getAllByText("人工补单")[0]).toBeInTheDocument();
|
||||
expect(grantButtons[0]).toBeDisabled();
|
||||
expect(verifyButtons[1]).toBeDisabled();
|
||||
});
|
||||
|
||||
test("zero coin seller recharge order uses makeup completion action", () => {
|
||||
const onGrant = vi.fn();
|
||||
const confirm = vi.fn(() => true);
|
||||
vi.stubGlobal("confirm", confirm);
|
||||
|
||||
render(
|
||||
<FinanceCoinSellerRechargeOrderList
|
||||
{...baseProps({
|
||||
orders: {
|
||||
items: [orderFixture({ coinAmount: 0, grantStatus: "pending", id: "makeup", verifyStatus: "verified" })],
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
total: 1,
|
||||
},
|
||||
})}
|
||||
onGrant={onGrant}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "完成补单" }));
|
||||
|
||||
expect(confirm).toHaveBeenCalledWith(expect.stringContaining("不发放金币"));
|
||||
expect(onGrant).toHaveBeenCalledWith(expect.objectContaining({ id: "makeup", coinAmount: 0 }));
|
||||
});
|
||||
|
||||
function baseProps(overrides = {}) {
|
||||
return {
|
||||
actionLoading: "",
|
||||
apps: [{ appCode: "lalu", appName: "lalu" }],
|
||||
canCreate: true,
|
||||
canGrant: true,
|
||||
canVerify: true,
|
||||
error: "",
|
||||
filters: {
|
||||
appCode: "",
|
||||
grantStatus: "",
|
||||
keyword: "",
|
||||
page: 1,
|
||||
providerCode: "",
|
||||
status: "",
|
||||
verifyStatus: "",
|
||||
},
|
||||
loading: false,
|
||||
onCreate: vi.fn(),
|
||||
onFiltersChange: vi.fn(),
|
||||
onGrant: vi.fn(),
|
||||
onReload: vi.fn(),
|
||||
onVerify: vi.fn(),
|
||||
onVerifyReceipt: vi.fn(),
|
||||
orders: {
|
||||
items: [orderFixture()],
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
total: 1,
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function orderFixture(overrides = {}) {
|
||||
return {
|
||||
appCode: "lalu",
|
||||
coinAmount: 100000,
|
||||
createdAtMs: 1760000000000,
|
||||
externalOrderNo: "MIFA-001",
|
||||
grantStatus: "pending",
|
||||
id: "order-1",
|
||||
providerCode: "mifapay",
|
||||
remark: "人工补单",
|
||||
status: "pending",
|
||||
targetDisplayUserId: "165075",
|
||||
targetUserId: "10001",
|
||||
usdAmount: "10.00",
|
||||
verifyStatus: "pending",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
@ -1,8 +1,12 @@
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { FinanceRechargeDetailList } from "./FinanceRechargeDetailList.jsx";
|
||||
|
||||
const baseProps = {
|
||||
apps: [
|
||||
{ appCode: "lalu", appName: "lalu" },
|
||||
{ appCode: "aslan", appName: "Aslan" },
|
||||
],
|
||||
bills: {
|
||||
items: [],
|
||||
page: 1,
|
||||
@ -20,12 +24,6 @@ const baseProps = {
|
||||
loading: false,
|
||||
onFiltersChange: vi.fn(),
|
||||
onReload: vi.fn(),
|
||||
options: {
|
||||
apps: [
|
||||
{ appCode: "lalu", appName: "lalu" },
|
||||
{ appCode: "aslan", appName: "Aslan" },
|
||||
],
|
||||
},
|
||||
summary: {
|
||||
googlePlay: { billCount: 1, coinAmount: 90000, usdMinorAmount: 999 },
|
||||
thirdParty: { billCount: 0, coinAmount: 0, usdMinorAmount: 0 },
|
||||
@ -46,18 +44,14 @@ test("hides coin recharge columns by default", () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("switch", { name: "金币显示" })).not.toBeChecked();
|
||||
expect(screen.queryByRole("columnheader", { name: "充值金币" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole("columnheader", { name: "金币换算" })).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("90,000 金币")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("90,000 金币 = USD 9.99")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("1 笔 · 90,000 金币")).not.toBeInTheDocument();
|
||||
expect(screen.getAllByText("1 笔")).toHaveLength(2);
|
||||
expect(screen.getByRole("columnheader", { name: "账单金额" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "用户实付" })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("renders app recharge coin columns and values when switch is enabled", () => {
|
||||
test("renders app recharge coin columns and values when coin currency is selected", () => {
|
||||
render(
|
||||
<FinanceRechargeDetailList
|
||||
{...baseProps}
|
||||
@ -66,39 +60,34 @@ test("renders app recharge coin columns and values when switch is enabled", () =
|
||||
items: [rechargeBillFixture()],
|
||||
total: 1,
|
||||
}}
|
||||
currency="coin"
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("switch", { name: "金币显示" }));
|
||||
|
||||
expect(screen.getByRole("columnheader", { name: "APP" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值金币" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值来源" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "充值订单号" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "金币换算" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "账单金额" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "用户实付" })).toBeInTheDocument();
|
||||
expect(screen.getByRole("columnheader", { name: "三方税率扣款" })).toBeInTheDocument();
|
||||
expect(screen.getByText("Aslan")).toBeInTheDocument();
|
||||
expect(screen.getByText("90,000 金币")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("1 笔 · 90,000 金币")).toHaveLength(2);
|
||||
expect(screen.getAllByText("谷歌充值").length).toBeGreaterThanOrEqual(1);
|
||||
expect(screen.getByText("tx-google")).toBeInTheDocument();
|
||||
expect(screen.getByText("cmd-1 / GPA.1")).toBeInTheDocument();
|
||||
expect(screen.getByText("90,000 金币 = USD 9.99")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("SAR 12.99")).toHaveLength(2);
|
||||
expect(screen.getByText("SAR 1.95 / 15%")).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: /充值时间 · 中国时区/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test("keeps empty recharge detail table colspan aligned with visible columns", () => {
|
||||
const { container } = render(<FinanceRechargeDetailList {...baseProps} />);
|
||||
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(7);
|
||||
expect(screen.getByText("当前无数据")).toBeInTheDocument();
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(8);
|
||||
expect(screen.getByText("当前筛选条件下没有账单")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("switch", { name: "金币显示" }));
|
||||
expect(container.querySelector("tbody td")?.colSpan).toBe(9);
|
||||
const { container: coinContainer } = render(<FinanceRechargeDetailList {...baseProps} currency="coin" />);
|
||||
expect(coinContainer.querySelector("tbody td")?.colSpan).toBe(9);
|
||||
});
|
||||
|
||||
function rechargeBillFixture() {
|
||||
|
||||
@ -7,6 +7,10 @@ import RuleFolderOutlined from "@mui/icons-material/RuleFolderOutlined";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
|
||||
const NAV_GROUPS = [
|
||||
{
|
||||
label: "订单",
|
||||
views: [{ icon: ReceiptLongOutlined, id: "coinSellerRechargeOrders", label: "币商充值" }],
|
||||
},
|
||||
{
|
||||
label: "经营",
|
||||
views: [
|
||||
@ -27,6 +31,7 @@ const NAV_GROUPS = [
|
||||
|
||||
const VIEW_TITLES = {
|
||||
applications: "申请审核",
|
||||
coinSellerRechargeOrders: "币商充值",
|
||||
create: "发起申请",
|
||||
overview: "财务概览",
|
||||
recon: "渠道对账",
|
||||
@ -39,6 +44,7 @@ export function FinanceShell({
|
||||
appBar,
|
||||
canAudit,
|
||||
canCreate,
|
||||
canViewCoinSellerRechargeOrders,
|
||||
canViewRechargeDetails,
|
||||
canViewWithdrawals,
|
||||
children,
|
||||
@ -52,6 +58,9 @@ export function FinanceShell({
|
||||
if (viewId === "overview" || viewId === "rechargeDetails" || viewId === "recon") {
|
||||
return canViewRechargeDetails;
|
||||
}
|
||||
if (viewId === "coinSellerRechargeOrders") {
|
||||
return canViewCoinSellerRechargeOrders;
|
||||
}
|
||||
if (viewId === "applications") {
|
||||
return canAudit;
|
||||
}
|
||||
|
||||
@ -2,6 +2,10 @@ export const FINANCE_PERMISSIONS = {
|
||||
auditApplication: "finance-application:audit",
|
||||
auditWithdrawalApplications: "finance-withdrawal:audit",
|
||||
createApplication: "finance-application:create",
|
||||
createCoinSellerRechargeOrder: "finance-order:coin-seller-recharge:create",
|
||||
grantCoinSellerRechargeOrder: "finance-order:coin-seller-recharge:grant",
|
||||
verifyCoinSellerRechargeOrder: "finance-order:coin-seller-recharge:verify",
|
||||
viewCoinSellerRechargeOrders: "finance-order:coin-seller-recharge:view",
|
||||
viewAppRechargeDetails: "payment-bill:view",
|
||||
viewWithdrawalApplications: "finance-withdrawal:view",
|
||||
};
|
||||
@ -55,6 +59,37 @@ export const APPLICATION_STATUS = [
|
||||
["rejected", "已拒绝"],
|
||||
];
|
||||
|
||||
export const COIN_SELLER_RECHARGE_PROVIDERS = [
|
||||
["mifapay", "MiFaPay"],
|
||||
["v5pay", "V5Pay"],
|
||||
["usdt", "USDT"],
|
||||
];
|
||||
|
||||
export const COIN_SELLER_RECHARGE_CHAINS = [
|
||||
["TRON", "TRON"],
|
||||
["BSC", "BSC"],
|
||||
];
|
||||
|
||||
export const COIN_SELLER_RECHARGE_STATUS = [
|
||||
["pending", "待处理"],
|
||||
["verified", "已校验"],
|
||||
["granted", "已发放"],
|
||||
["failed", "失败"],
|
||||
["canceled", "已取消"],
|
||||
];
|
||||
|
||||
export const COIN_SELLER_RECHARGE_VERIFY_STATUS = [
|
||||
["pending", "待校验"],
|
||||
["verified", "校验通过"],
|
||||
["failed", "校验失败"],
|
||||
];
|
||||
|
||||
export const COIN_SELLER_RECHARGE_GRANT_STATUS = [
|
||||
["pending", "待发放"],
|
||||
["granted", "已发放"],
|
||||
["failed", "发放失败"],
|
||||
];
|
||||
|
||||
export const DEFAULT_APPLICATION_FORM = {
|
||||
appCode: "",
|
||||
coinAmount: "",
|
||||
@ -66,6 +101,17 @@ export const DEFAULT_APPLICATION_FORM = {
|
||||
walletIdentity: "",
|
||||
};
|
||||
|
||||
export const DEFAULT_COIN_SELLER_RECHARGE_ORDER_FORM = {
|
||||
appCode: "",
|
||||
chain: "",
|
||||
coinAmount: "",
|
||||
externalOrderNo: "",
|
||||
providerCode: "mifapay",
|
||||
remark: "",
|
||||
targetUserId: "",
|
||||
usdAmount: "",
|
||||
};
|
||||
|
||||
export function operationRequiresWalletIdentity(operation) {
|
||||
return FINANCE_OPERATIONS.some((item) => item.value === operation && item.requiresWalletIdentity);
|
||||
}
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import {
|
||||
APPLICATION_STATUS,
|
||||
COIN_SELLER_RECHARGE_CHAINS,
|
||||
COIN_SELLER_RECHARGE_GRANT_STATUS,
|
||||
COIN_SELLER_RECHARGE_PROVIDERS,
|
||||
COIN_SELLER_RECHARGE_STATUS,
|
||||
COIN_SELLER_RECHARGE_VERIFY_STATUS,
|
||||
FINANCE_OPERATIONS,
|
||||
WALLET_IDENTITIES,
|
||||
operationAllowsZeroRechargeAmount,
|
||||
@ -122,6 +127,110 @@ export function normalizeWithdrawalApplicationPage(data = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
export function buildCoinSellerRechargeOrderPayload(form) {
|
||||
const providerCode = stringValue(form.providerCode);
|
||||
const payload = {
|
||||
appCode: stringValue(form.appCode),
|
||||
chain: providerCode === "usdt" ? stringValue(form.chain).toUpperCase() : "",
|
||||
coinAmount: numberValue(form.coinAmount),
|
||||
externalOrderNo: stringValue(form.externalOrderNo),
|
||||
providerCode,
|
||||
remark: stringValue(form.remark),
|
||||
targetUserId: stringValue(form.targetUserId),
|
||||
usdAmount: numberValue(form.usdAmount),
|
||||
};
|
||||
|
||||
return Object.fromEntries(Object.entries(payload).filter(([, value]) => value !== ""));
|
||||
}
|
||||
|
||||
export function buildCoinSellerRechargeReceiptPayload(form) {
|
||||
const providerCode = stringValue(form.providerCode);
|
||||
return Object.fromEntries(
|
||||
Object.entries({
|
||||
appCode: stringValue(form.appCode),
|
||||
chain: providerCode === "usdt" ? stringValue(form.chain).toUpperCase() : "",
|
||||
externalOrderNo: stringValue(form.externalOrderNo),
|
||||
providerCode,
|
||||
usdAmount: numberValue(form.usdAmount),
|
||||
}).filter(([, value]) => value !== ""),
|
||||
);
|
||||
}
|
||||
|
||||
export function validateCoinSellerRechargeOrderPayload(payload) {
|
||||
if (!payload.appCode) {
|
||||
return "请选择 APP";
|
||||
}
|
||||
if (!payload.targetUserId) {
|
||||
return "请填写目标用户 ID";
|
||||
}
|
||||
if (!Number.isFinite(payload.usdAmount) || payload.usdAmount <= 0) {
|
||||
return "请填写大于 0 的 USD 金额";
|
||||
}
|
||||
if (!Number.isInteger(payload.coinAmount) || payload.coinAmount < 0) {
|
||||
return "请填写不小于 0 的整数金币";
|
||||
}
|
||||
if (!payload.providerCode) {
|
||||
return "请选择支付渠道";
|
||||
}
|
||||
if (!payload.externalOrderNo) {
|
||||
return "请填写三方订单号";
|
||||
}
|
||||
if (payload.providerCode === "usdt" && !payload.chain) {
|
||||
return "请选择 USDT 链";
|
||||
}
|
||||
if (payload.remark && payload.remark.length > 512) {
|
||||
return "备注不能超过 512 字";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
export function normalizeCoinSellerRechargeOrder(item = {}) {
|
||||
return {
|
||||
appCode: stringValue(item.appCode ?? item.app_code),
|
||||
chain: stringValue(item.chain),
|
||||
coinAmount: numberValue(item.coinAmount ?? item.coin_amount),
|
||||
createdAtMs: numberOrNull(item.createdAtMs ?? item.created_at_ms),
|
||||
externalOrderNo: stringValue(item.externalOrderNo ?? item.external_order_no),
|
||||
failureReason: stringValue(item.failureReason ?? item.failure_reason),
|
||||
grantStatus: stringValue(item.grantStatus ?? item.grant_status) || "pending",
|
||||
grantedAtMs: numberOrNull(item.grantedAtMs ?? item.granted_at_ms),
|
||||
grantedByName: stringValue(item.grantedByName ?? item.granted_by_name),
|
||||
id: stringValue(item.id ?? item.orderId ?? item.order_id),
|
||||
operatorName: stringValue(item.operatorName ?? item.operator_name),
|
||||
providerAmountMinor: numberOrNull(item.providerAmountMinor ?? item.provider_amount_minor),
|
||||
providerCode: stringValue(item.providerCode ?? item.provider_code),
|
||||
providerCurrencyCode: stringValue(item.providerCurrencyCode ?? item.provider_currency_code),
|
||||
providerOrderId: stringValue(item.providerOrderId ?? item.provider_order_id),
|
||||
providerPayloadJson: stringValue(item.providerPayloadJson ?? item.provider_payload_json),
|
||||
providerStatus: stringValue(item.providerStatus ?? item.provider_status),
|
||||
receiveAddress: stringValue(item.receiveAddress ?? item.receive_address),
|
||||
remark: stringValue(item.remark),
|
||||
status: stringValue(item.status) || "pending",
|
||||
targetDisplayUserId: stringValue(item.targetDisplayUserId ?? item.target_display_user_id),
|
||||
targetUserId: stringValue(item.targetUserId ?? item.target_user_id),
|
||||
usdAmount: item.usdAmount ?? item.usd_amount ?? "",
|
||||
usdMinorAmount: numberOrNull(item.usdMinorAmount ?? item.usd_minor_amount),
|
||||
verifiedAtMs: numberOrNull(item.verifiedAtMs ?? item.verified_at_ms),
|
||||
verifiedByName: stringValue(item.verifiedByName ?? item.verified_by_name),
|
||||
verifyStatus: stringValue(item.verifyStatus ?? item.verify_status) || "pending",
|
||||
walletAmountDelta: numberOrNull(item.walletAmountDelta ?? item.wallet_amount_delta),
|
||||
walletAssetType: stringValue(item.walletAssetType ?? item.wallet_asset_type),
|
||||
walletBalanceAfter: numberOrNull(item.walletBalanceAfter ?? item.wallet_balance_after),
|
||||
walletCommandId: stringValue(item.walletCommandId ?? item.wallet_command_id),
|
||||
walletTransactionId: stringValue(item.walletTransactionId ?? item.wallet_transaction_id),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeCoinSellerRechargeOrderPage(data = {}) {
|
||||
const items = Array.isArray(data.items) ? data.items : [];
|
||||
return {
|
||||
items: items.map(normalizeCoinSellerRechargeOrder),
|
||||
page: Number(data.page || 1),
|
||||
pageSize: Number(data.pageSize ?? data.page_size ?? items.length),
|
||||
total: Number(data.total ?? items.length),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeRechargeBill(item = {}) {
|
||||
return {
|
||||
appCode: stringValue(item.appCode ?? item.app_code),
|
||||
@ -414,6 +523,50 @@ export function statusLabel(value) {
|
||||
return APPLICATION_STATUS.find(([status]) => status === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
export function coinSellerRechargeProviderLabel(value) {
|
||||
return COIN_SELLER_RECHARGE_PROVIDERS.find(([code]) => code === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
export function coinSellerRechargeChainLabel(value) {
|
||||
return COIN_SELLER_RECHARGE_CHAINS.find(([code]) => code === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
export function coinSellerRechargeStatusLabel(value) {
|
||||
return COIN_SELLER_RECHARGE_STATUS.find(([status]) => status === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
export function coinSellerRechargeVerifyStatusLabel(value) {
|
||||
return COIN_SELLER_RECHARGE_VERIFY_STATUS.find(([status]) => status === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
export function coinSellerRechargeGrantStatusLabel(value) {
|
||||
return COIN_SELLER_RECHARGE_GRANT_STATUS.find(([status]) => status === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
export function coinSellerRechargeStatusTone(value) {
|
||||
if (["granted", "verified", "success", "succeeded", "completed", "passed"].includes(stringValue(value))) {
|
||||
return "success";
|
||||
}
|
||||
if (["failed", "rejected", "canceled", "cancelled"].includes(stringValue(value))) {
|
||||
return "danger";
|
||||
}
|
||||
return "warning";
|
||||
}
|
||||
|
||||
export function isCoinSellerRechargeVerified(order) {
|
||||
return (
|
||||
["verified", "success", "succeeded", "completed", "passed"].includes(stringValue(order?.verifyStatus)) ||
|
||||
stringValue(order?.status) === "verified"
|
||||
);
|
||||
}
|
||||
|
||||
export function isCoinSellerRechargeGranted(order) {
|
||||
return (
|
||||
["granted", "success", "succeeded", "completed"].includes(stringValue(order?.grantStatus)) ||
|
||||
stringValue(order?.status) === "granted"
|
||||
);
|
||||
}
|
||||
|
||||
export function walletIdentityLabel(value) {
|
||||
return WALLET_IDENTITIES.find(([identity]) => identity === value)?.[1] || value || "-";
|
||||
}
|
||||
|
||||
@ -1,13 +1,18 @@
|
||||
import { expect, test } from "vitest";
|
||||
import {
|
||||
buildApplicationPayload,
|
||||
buildCoinSellerRechargeReceiptPayload,
|
||||
buildCoinSellerRechargeOrderPayload,
|
||||
coinSellerRechargeProviderLabel,
|
||||
normalizeApplicationPage,
|
||||
normalizeCoinSellerRechargeOrderPage,
|
||||
normalizeRechargeBillPage,
|
||||
normalizeWithdrawalApplicationPage,
|
||||
operationLabel,
|
||||
rechargeTypeLabel,
|
||||
statusLabel,
|
||||
validateApplicationPayload,
|
||||
validateCoinSellerRechargeOrderPayload,
|
||||
walletIdentityLabel,
|
||||
} from "./format.js";
|
||||
|
||||
@ -160,6 +165,84 @@ test("normalizes finance withdrawal application list rows", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("builds validates and normalizes coin seller recharge orders", () => {
|
||||
const payload = buildCoinSellerRechargeOrderPayload({
|
||||
appCode: " lalu ",
|
||||
coinAmount: "100000",
|
||||
externalOrderNo: " MIFA-001 ",
|
||||
providerCode: "mifapay",
|
||||
remark: " 人工补单 ",
|
||||
targetUserId: " 10001 ",
|
||||
usdAmount: "10.5",
|
||||
});
|
||||
|
||||
expect(payload).toEqual({
|
||||
appCode: "lalu",
|
||||
coinAmount: 100000,
|
||||
externalOrderNo: "MIFA-001",
|
||||
providerCode: "mifapay",
|
||||
remark: "人工补单",
|
||||
targetUserId: "10001",
|
||||
usdAmount: 10.5,
|
||||
});
|
||||
expect(
|
||||
buildCoinSellerRechargeReceiptPayload({
|
||||
...payload,
|
||||
coinAmount: "",
|
||||
targetUserId: "",
|
||||
}),
|
||||
).toEqual({
|
||||
appCode: "lalu",
|
||||
externalOrderNo: "MIFA-001",
|
||||
providerCode: "mifapay",
|
||||
usdAmount: 10.5,
|
||||
});
|
||||
expect(validateCoinSellerRechargeOrderPayload(payload)).toBe("");
|
||||
expect(validateCoinSellerRechargeOrderPayload({ ...payload, coinAmount: 0 })).toBe("");
|
||||
expect(validateCoinSellerRechargeOrderPayload({ ...payload, coinAmount: -1 })).toBe("请填写不小于 0 的整数金币");
|
||||
expect(validateCoinSellerRechargeOrderPayload({ ...payload, remark: "x".repeat(513) })).toBe("备注不能超过 512 字");
|
||||
expect(validateCoinSellerRechargeOrderPayload({ ...payload, providerCode: "usdt", chain: "" })).toBe(
|
||||
"请选择 USDT 链",
|
||||
);
|
||||
|
||||
const page = normalizeCoinSellerRechargeOrderPage({
|
||||
items: [
|
||||
{
|
||||
app_code: "lalu",
|
||||
coin_amount: 100000,
|
||||
created_at_ms: 1760000000000,
|
||||
external_order_no: "MIFA-001",
|
||||
grant_status: "pending",
|
||||
id: "order-1",
|
||||
provider_code: "mifapay",
|
||||
remark: "人工补单",
|
||||
target_display_user_id: "165075",
|
||||
target_user_id: "10001",
|
||||
usd_amount: "10.50",
|
||||
verify_status: "verified",
|
||||
wallet_transaction_id: "wallet-tx",
|
||||
},
|
||||
],
|
||||
page: 1,
|
||||
page_size: 50,
|
||||
total: 1,
|
||||
});
|
||||
|
||||
expect(page.items[0]).toMatchObject({
|
||||
appCode: "lalu",
|
||||
coinAmount: 100000,
|
||||
externalOrderNo: "MIFA-001",
|
||||
id: "order-1",
|
||||
providerCode: "mifapay",
|
||||
remark: "人工补单",
|
||||
targetDisplayUserId: "165075",
|
||||
targetUserId: "10001",
|
||||
verifyStatus: "verified",
|
||||
walletTransactionId: "wallet-tx",
|
||||
});
|
||||
expect(coinSellerRechargeProviderLabel("mifapay")).toBe("MiFaPay");
|
||||
});
|
||||
|
||||
test("normalizes app recharge detail list rows with optional payment fields", () => {
|
||||
const page = normalizeRechargeBillPage({
|
||||
items: [
|
||||
|
||||
@ -386,6 +386,13 @@ a {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.finance-remark-cell {
|
||||
max-width: 180px;
|
||||
color: var(--text-secondary);
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.finance-pagination__meta {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
@ -528,6 +535,11 @@ a {
|
||||
border-radius: var(--radius-card) !important;
|
||||
}
|
||||
|
||||
.finance-create-dialog-paper--coin-seller {
|
||||
width: min(760px, calc(100vw - 64px));
|
||||
max-width: min(760px, calc(100vw - 64px)) !important;
|
||||
}
|
||||
|
||||
.finance-create-dialog-paper .MuiDialogTitle-root {
|
||||
padding: var(--space-5) var(--space-5) var(--space-2);
|
||||
font-size: 18px;
|
||||
@ -544,6 +556,25 @@ a {
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.finance-form--dialog .finance-form-grid--coin-seller-create {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.finance-form--dialog .finance-form-grid--coin-seller-no-chain {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.finance-receipt-field {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
|
||||
.finance-receipt-result {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.finance-form--dialog .finance-evidence-grid {
|
||||
grid-template-columns: 320px minmax(0, 1fr);
|
||||
gap: var(--space-4);
|
||||
@ -648,6 +679,8 @@ a {
|
||||
}
|
||||
|
||||
.finance-form--dialog .finance-form-grid,
|
||||
.finance-form--dialog .finance-form-grid--coin-seller-create,
|
||||
.finance-form--dialog .finance-form-grid--coin-seller-no-chain,
|
||||
.finance-form--dialog .finance-evidence-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@ -1072,6 +1105,10 @@ a {
|
||||
min-width: 1180px;
|
||||
}
|
||||
|
||||
.finance-flat-table--orders {
|
||||
min-width: 1740px;
|
||||
}
|
||||
|
||||
.finance-views {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
|
||||
@ -47,6 +47,10 @@ export const PERMISSIONS = {
|
||||
financeApplicationAudit: "finance-application:audit",
|
||||
financeWithdrawalView: "finance-withdrawal:view",
|
||||
financeWithdrawalAudit: "finance-withdrawal:audit",
|
||||
financeOrderCoinSellerRechargeView: "finance-order:coin-seller-recharge:view",
|
||||
financeOrderCoinSellerRechargeCreate: "finance-order:coin-seller-recharge:create",
|
||||
financeOrderCoinSellerRechargeVerify: "finance-order:coin-seller-recharge:verify",
|
||||
financeOrderCoinSellerRechargeGrant: "finance-order:coin-seller-recharge:grant",
|
||||
financeOperationUserCoinCredit: "finance-operation:user-coin-credit",
|
||||
financeOperationUserCoinDebit: "finance-operation:user-coin-debit",
|
||||
financeOperationUserWalletCredit: "finance-operation:user-wallet-credit",
|
||||
|
||||
@ -45,6 +45,7 @@ export const API_OPERATIONS = {
|
||||
createEmojiPack: "createEmojiPack",
|
||||
createExploreTab: "createExploreTab",
|
||||
createFinanceApplication: "createFinanceApplication",
|
||||
createFinanceCoinSellerRechargeOrder: "createFinanceCoinSellerRechargeOrder",
|
||||
createFullServerNoticeFanout: "createFullServerNoticeFanout",
|
||||
createGift: "createGift",
|
||||
createH5Link: "createH5Link",
|
||||
@ -151,6 +152,7 @@ export const API_OPERATIONS = {
|
||||
getWeeklyStarCycle: "getWeeklyStarCycle",
|
||||
getWheelConfig: "getWheelConfig",
|
||||
getWheelDrawSummary: "getWheelDrawSummary",
|
||||
grantFinanceCoinSellerRechargeOrder: "grantFinanceCoinSellerRechargeOrder",
|
||||
grantPrettyId: "grantPrettyId",
|
||||
grantResource: "grantResource",
|
||||
grantResourceGroup: "grantResourceGroup",
|
||||
@ -177,6 +179,7 @@ export const API_OPERATIONS = {
|
||||
listEmojiPacks: "listEmojiPacks",
|
||||
listExploreTabs: "listExploreTabs",
|
||||
listFinanceApplications: "listFinanceApplications",
|
||||
listFinanceCoinSellerRechargeOrders: "listFinanceCoinSellerRechargeOrders",
|
||||
listFinanceWithdrawalApplications: "listFinanceWithdrawalApplications",
|
||||
listFirstRechargeRewardClaims: "listFirstRechargeRewardClaims",
|
||||
listGifts: "listGifts",
|
||||
@ -339,7 +342,9 @@ export const API_OPERATIONS = {
|
||||
upsertLuckyGiftConfig: "upsertLuckyGiftConfig",
|
||||
upsertResourceShopItems: "upsertResourceShopItems",
|
||||
upsertRule: "upsertRule",
|
||||
upsertWheelConfig: "upsertWheelConfig"
|
||||
upsertWheelConfig: "upsertWheelConfig",
|
||||
verifyFinanceCoinSellerRechargeOrder: "verifyFinanceCoinSellerRechargeOrder",
|
||||
verifyFinanceCoinSellerRechargeReceipt: "verifyFinanceCoinSellerRechargeReceipt"
|
||||
} as const;
|
||||
|
||||
export type ApiOperationId = (typeof API_OPERATIONS)[keyof typeof API_OPERATIONS];
|
||||
@ -567,6 +572,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
permission: "finance-application:create",
|
||||
permissions: ["finance-application:create"]
|
||||
},
|
||||
createFinanceCoinSellerRechargeOrder: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.createFinanceCoinSellerRechargeOrder,
|
||||
path: "/v1/admin/finance/orders/coin-seller-recharges",
|
||||
permission: "finance-order:coin-seller-recharge:create",
|
||||
permissions: ["finance-order:coin-seller-recharge:create"]
|
||||
},
|
||||
createFullServerNoticeFanout: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.createFullServerNoticeFanout,
|
||||
@ -1309,6 +1321,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
permission: "wheel:view",
|
||||
permissions: ["wheel:view"]
|
||||
},
|
||||
grantFinanceCoinSellerRechargeOrder: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.grantFinanceCoinSellerRechargeOrder,
|
||||
path: "/v1/admin/finance/orders/coin-seller-recharges/{order_id}/grant",
|
||||
permission: "finance-order:coin-seller-recharge:grant",
|
||||
permissions: ["finance-order:coin-seller-recharge:grant"]
|
||||
},
|
||||
grantPrettyId: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.grantPrettyId,
|
||||
@ -1489,6 +1508,13 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
permission: "finance-application:audit",
|
||||
permissions: ["finance-application:audit"]
|
||||
},
|
||||
listFinanceCoinSellerRechargeOrders: {
|
||||
method: "GET",
|
||||
operationId: API_OPERATIONS.listFinanceCoinSellerRechargeOrders,
|
||||
path: "/v1/admin/finance/orders/coin-seller-recharges",
|
||||
permission: "finance-order:coin-seller-recharge:view",
|
||||
permissions: ["finance-order:coin-seller-recharge:view"]
|
||||
},
|
||||
listFinanceWithdrawalApplications: {
|
||||
method: "GET",
|
||||
operationId: API_OPERATIONS.listFinanceWithdrawalApplications,
|
||||
@ -2617,6 +2643,20 @@ export const API_ENDPOINTS: Record<ApiOperationId, ApiEndpoint> = {
|
||||
path: "/v1/admin/activity/wheel/config",
|
||||
permission: "wheel:update",
|
||||
permissions: ["wheel:update"]
|
||||
},
|
||||
verifyFinanceCoinSellerRechargeOrder: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.verifyFinanceCoinSellerRechargeOrder,
|
||||
path: "/v1/admin/finance/orders/coin-seller-recharges/{order_id}/verify",
|
||||
permission: "finance-order:coin-seller-recharge:verify",
|
||||
permissions: ["finance-order:coin-seller-recharge:verify"]
|
||||
},
|
||||
verifyFinanceCoinSellerRechargeReceipt: {
|
||||
method: "POST",
|
||||
operationId: API_OPERATIONS.verifyFinanceCoinSellerRechargeReceipt,
|
||||
path: "/v1/admin/finance/orders/coin-seller-recharges/receipt-verifications",
|
||||
permission: "finance-order:coin-seller-recharge:verify",
|
||||
permissions: ["finance-order:coin-seller-recharge:verify"]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
265
src/shared/api/generated/schema.d.ts
vendored
265
src/shared/api/generated/schema.d.ts
vendored
@ -2228,6 +2228,70 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/finance/orders/coin-seller-recharges": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get: operations["listFinanceCoinSellerRechargeOrders"];
|
||||
put?: never;
|
||||
post: operations["createFinanceCoinSellerRechargeOrder"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/finance/orders/coin-seller-recharges/receipt-verifications": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["verifyFinanceCoinSellerRechargeReceipt"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/finance/orders/coin-seller-recharges/{order_id}/verify": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["verifyFinanceCoinSellerRechargeOrder"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/finance/orders/coin-seller-recharges/{order_id}/grant": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
get?: never;
|
||||
put?: never;
|
||||
post: operations["grantFinanceCoinSellerRechargeOrder"];
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/admin/payment/temporary-links": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@ -3890,6 +3954,12 @@ export interface components {
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
ApiPageFinanceCoinSellerRechargeOrder: {
|
||||
items: components["schemas"]["FinanceCoinSellerRechargeOrder"][];
|
||||
page: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
ApiPageFinanceWithdrawalApplication: {
|
||||
items: components["schemas"]["FinanceWithdrawalApplication"][];
|
||||
page: number;
|
||||
@ -3938,6 +4008,15 @@ export interface components {
|
||||
ApiResponseFinanceApplicationPage: components["schemas"]["Envelope"] & {
|
||||
data?: components["schemas"]["ApiPageFinanceApplication"];
|
||||
};
|
||||
ApiResponseFinanceCoinSellerRechargeOrder: components["schemas"]["Envelope"] & {
|
||||
data?: components["schemas"]["FinanceCoinSellerRechargeOrder"];
|
||||
};
|
||||
ApiResponseFinanceCoinSellerRechargeReceiptVerification: components["schemas"]["Envelope"] & {
|
||||
data?: components["schemas"]["FinanceCoinSellerRechargeReceiptVerification"];
|
||||
};
|
||||
ApiResponseFinanceCoinSellerRechargeOrderPage: components["schemas"]["Envelope"] & {
|
||||
data?: components["schemas"]["ApiPageFinanceCoinSellerRechargeOrder"];
|
||||
};
|
||||
ApiResponseFinanceWithdrawalApplication: components["schemas"]["Envelope"] & {
|
||||
data?: components["schemas"]["FinanceWithdrawalApplication"];
|
||||
};
|
||||
@ -4295,6 +4374,45 @@ export interface components {
|
||||
updatedAtMs: number;
|
||||
dingTalkNotified?: boolean;
|
||||
};
|
||||
FinanceCoinSellerRechargeOrder: {
|
||||
id: string;
|
||||
appCode: string;
|
||||
targetUserId: string;
|
||||
targetDisplayUserId?: string;
|
||||
usdAmount: string;
|
||||
usdMinorAmount?: number;
|
||||
coinAmount: number;
|
||||
/** @enum {string} */
|
||||
providerCode: "mifapay" | "v5pay" | "usdt";
|
||||
/** @enum {string} */
|
||||
chain?: "TRON" | "BSC";
|
||||
externalOrderNo: string;
|
||||
providerCountryCode?: string;
|
||||
providerCurrencyCode?: string;
|
||||
providerAmountMinor?: number;
|
||||
providerOrderId?: string;
|
||||
providerStatus?: string;
|
||||
providerPayType?: string;
|
||||
receiveAddress?: string;
|
||||
orderDate?: string;
|
||||
status: string;
|
||||
verifyStatus: string;
|
||||
grantStatus: string;
|
||||
remark?: string;
|
||||
failureReason?: string;
|
||||
providerPayloadJson?: string;
|
||||
walletCommandId?: string;
|
||||
walletTransactionId?: string;
|
||||
walletAssetType?: string;
|
||||
walletAmountDelta?: number;
|
||||
walletBalanceAfter?: number;
|
||||
operatorName?: string;
|
||||
verifiedByName?: string;
|
||||
grantedByName?: string;
|
||||
createdAtMs: number;
|
||||
verifiedAtMs?: number | null;
|
||||
grantedAtMs?: number | null;
|
||||
};
|
||||
FinanceWithdrawalApplication: {
|
||||
id: number;
|
||||
appCode: string;
|
||||
@ -4328,6 +4446,43 @@ export interface components {
|
||||
credentialImageUrl?: string;
|
||||
credentialText?: string;
|
||||
};
|
||||
FinanceCoinSellerRechargeOrderInput: {
|
||||
appCode: string;
|
||||
targetUserId: string;
|
||||
usdAmount: number;
|
||||
coinAmount: number;
|
||||
/** @enum {string} */
|
||||
providerCode: "mifapay" | "v5pay" | "usdt";
|
||||
externalOrderNo: string;
|
||||
/** @enum {string} */
|
||||
chain?: "TRON" | "BSC";
|
||||
remark?: string;
|
||||
};
|
||||
FinanceCoinSellerRechargeReceiptVerification: {
|
||||
verified: boolean;
|
||||
providerCode?: string;
|
||||
externalOrderNo?: string;
|
||||
providerOrderId?: string;
|
||||
status?: string;
|
||||
currencyCode?: string;
|
||||
providerAmountMinor?: number;
|
||||
/** @enum {string} */
|
||||
chain?: "TRON" | "BSC";
|
||||
receiveAddress?: string;
|
||||
verifiedAtMs?: number;
|
||||
failureReason?: string;
|
||||
rawJson?: string;
|
||||
};
|
||||
FinanceCoinSellerRechargeReceiptVerificationInput: {
|
||||
appCode: string;
|
||||
/** @enum {string} */
|
||||
providerCode: "mifapay" | "v5pay" | "usdt";
|
||||
externalOrderNo: string;
|
||||
/** @enum {string} */
|
||||
chain?: "TRON" | "BSC";
|
||||
usdAmount?: number;
|
||||
usdMinorAmount?: number;
|
||||
};
|
||||
UserFinanceScope: {
|
||||
appCode: string;
|
||||
regionId: number;
|
||||
@ -4425,6 +4580,33 @@ export interface components {
|
||||
};
|
||||
};
|
||||
/** @description OK */
|
||||
FinanceCoinSellerRechargeOrderPageResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiResponseFinanceCoinSellerRechargeOrderPage"];
|
||||
};
|
||||
};
|
||||
/** @description OK */
|
||||
FinanceCoinSellerRechargeOrderResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiResponseFinanceCoinSellerRechargeOrder"];
|
||||
};
|
||||
};
|
||||
/** @description OK */
|
||||
FinanceCoinSellerRechargeReceiptVerificationResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["ApiResponseFinanceCoinSellerRechargeReceiptVerification"];
|
||||
};
|
||||
};
|
||||
/** @description OK */
|
||||
FinanceWithdrawalApplicationPageResponse: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
@ -4667,6 +4849,16 @@ export interface components {
|
||||
"application/json": components["schemas"]["FinanceApplicationInput"];
|
||||
};
|
||||
};
|
||||
FinanceCoinSellerRechargeOrderRequest: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["FinanceCoinSellerRechargeOrderInput"];
|
||||
};
|
||||
};
|
||||
FinanceCoinSellerRechargeReceiptVerificationRequest: {
|
||||
content: {
|
||||
"application/json": components["schemas"]["FinanceCoinSellerRechargeReceiptVerificationInput"];
|
||||
};
|
||||
};
|
||||
FinanceApplicationAuditRequest: {
|
||||
content: {
|
||||
"application/json": {
|
||||
@ -7403,6 +7595,79 @@ export interface operations {
|
||||
200: components["responses"]["FinanceWithdrawalApplicationResponse"];
|
||||
};
|
||||
};
|
||||
listFinanceCoinSellerRechargeOrders: {
|
||||
parameters: {
|
||||
query?: {
|
||||
app_code?: string;
|
||||
provider_code?: string;
|
||||
status?: string;
|
||||
verify_status?: string;
|
||||
grant_status?: string;
|
||||
keyword?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
};
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["FinanceCoinSellerRechargeOrderPageResponse"];
|
||||
};
|
||||
};
|
||||
createFinanceCoinSellerRechargeOrder: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: components["requestBodies"]["FinanceCoinSellerRechargeOrderRequest"];
|
||||
responses: {
|
||||
201: components["responses"]["FinanceCoinSellerRechargeOrderResponse"];
|
||||
};
|
||||
};
|
||||
verifyFinanceCoinSellerRechargeReceipt: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: components["requestBodies"]["FinanceCoinSellerRechargeReceiptVerificationRequest"];
|
||||
responses: {
|
||||
200: components["responses"]["FinanceCoinSellerRechargeReceiptVerificationResponse"];
|
||||
};
|
||||
};
|
||||
verifyFinanceCoinSellerRechargeOrder: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
order_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["FinanceCoinSellerRechargeOrderResponse"];
|
||||
};
|
||||
};
|
||||
grantFinanceCoinSellerRechargeOrder: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path: {
|
||||
order_id: string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
200: components["responses"]["FinanceCoinSellerRechargeOrderResponse"];
|
||||
};
|
||||
};
|
||||
listTemporaryPaymentLinks: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@ -585,6 +585,79 @@ export interface TemporaryPaymentLinkDto {
|
||||
usdMinorAmount: number;
|
||||
}
|
||||
|
||||
export interface FinanceCoinSellerRechargeOrderDto {
|
||||
appCode?: string;
|
||||
chain?: string;
|
||||
coinAmount: number;
|
||||
createdAtMs?: number;
|
||||
externalOrderNo?: string;
|
||||
failureReason?: string;
|
||||
grantStatus?: string;
|
||||
grantedAtMs?: number;
|
||||
grantedByName?: string;
|
||||
id: EntityId;
|
||||
operatorName?: string;
|
||||
orderDate?: string;
|
||||
providerAmountMinor?: number;
|
||||
providerCode?: string;
|
||||
providerCountryCode?: string;
|
||||
providerCurrencyCode?: string;
|
||||
providerOrderId?: string;
|
||||
providerPayloadJson?: string;
|
||||
providerPayType?: string;
|
||||
providerStatus?: string;
|
||||
receiveAddress?: string;
|
||||
remark?: string;
|
||||
status?: string;
|
||||
targetDisplayUserId?: string;
|
||||
targetUserId: string;
|
||||
usdAmount?: number | string;
|
||||
usdMinorAmount?: number;
|
||||
verifiedAtMs?: number;
|
||||
verifiedByName?: string;
|
||||
verifyStatus?: string;
|
||||
walletAmountDelta?: number;
|
||||
walletAssetType?: string;
|
||||
walletBalanceAfter?: number;
|
||||
walletCommandId?: string;
|
||||
walletTransactionId?: string;
|
||||
}
|
||||
|
||||
export interface FinanceCoinSellerRechargeOrderPayload {
|
||||
appCode: string;
|
||||
chain?: string;
|
||||
coinAmount: number;
|
||||
externalOrderNo: string;
|
||||
providerCode: "mifapay" | "v5pay" | "usdt";
|
||||
remark?: string;
|
||||
targetUserId: EntityId;
|
||||
usdAmount: number;
|
||||
}
|
||||
|
||||
export interface FinanceCoinSellerRechargeReceiptVerificationPayload {
|
||||
appCode: string;
|
||||
chain?: string;
|
||||
externalOrderNo: string;
|
||||
providerCode: "mifapay" | "v5pay" | "usdt";
|
||||
usdAmount?: number;
|
||||
usdMinorAmount?: number;
|
||||
}
|
||||
|
||||
export interface FinanceCoinSellerRechargeReceiptVerificationDto {
|
||||
chain?: string;
|
||||
currencyCode?: string;
|
||||
externalOrderNo?: string;
|
||||
failureReason?: string;
|
||||
providerAmountMinor?: number;
|
||||
providerCode?: string;
|
||||
providerOrderId?: string;
|
||||
rawJson?: string;
|
||||
receiveAddress?: string;
|
||||
status?: string;
|
||||
verifiedAtMs?: number;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export interface ThirdPartyPaymentRateSyncDto {
|
||||
missingCurrencies?: string[];
|
||||
rates?: Record<string, string>;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user