|
|
@@ -253,6 +253,113 @@
|
|
|
</EnhancedDialog>
|
|
|
</el-tab-pane>
|
|
|
|
|
|
+ <!-- 应用配置 -->
|
|
|
+ <el-tab-pane label="应用配置" name="config">
|
|
|
+ <div class="config-layout">
|
|
|
+ <!-- 查询中显示loading -->
|
|
|
+ <el-card v-if="configLoading" v-loading="configLoading" class="config-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>开放平台配置</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 配置不存在时显示创建按钮 -->
|
|
|
+ <el-card v-else-if="!configExists" class="config-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>开放平台配置</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <div class="config-empty">
|
|
|
+ <el-empty description="暂无开放平台配置" />
|
|
|
+ <p style="margin: 16px 0; color: #606266;">
|
|
|
+ 请先创建开放平台配置,才能收到平台回调通知。
|
|
|
+ </p>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ :loading="configLoading"
|
|
|
+ @click="handleCreateConfig"
|
|
|
+ >
|
|
|
+ 创建配置
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 配置存在时显示编辑表单 -->
|
|
|
+ <el-card v-else class="config-card">
|
|
|
+ <template #header>
|
|
|
+ <div class="card-header">
|
|
|
+ <span>开放平台配置</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-form
|
|
|
+ ref="configFormRef"
|
|
|
+ :model="configForm"
|
|
|
+ label-width="140px"
|
|
|
+ label-position="right"
|
|
|
+ style="max-width: 600px"
|
|
|
+ >
|
|
|
+ <el-form-item label="应用ID">
|
|
|
+ <el-input v-model="configForm.app_id" disabled>
|
|
|
+ <template #append>
|
|
|
+ <el-button @click="copyToClipboard(configForm.app_id)">
|
|
|
+ <el-icon><CopyDocument /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="网关地址">
|
|
|
+ <el-input v-model="configForm.gateway_url" disabled>
|
|
|
+ <template #append>
|
|
|
+ <el-button @click="copyToClipboard(configForm.gateway_url)">
|
|
|
+ <el-icon><CopyDocument /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="异步通知地址">
|
|
|
+ <el-input
|
|
|
+ v-model="configForm.notify_url"
|
|
|
+ placeholder="请输入异步通知地址"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item label="同步返回地址">
|
|
|
+ <el-input
|
|
|
+ v-model="configForm.return_url"
|
|
|
+ placeholder="请输入同步返回地址"
|
|
|
+ clearable
|
|
|
+ />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态">
|
|
|
+ <el-tag :type="configForm.status === 'ENABLED' ? 'success' : 'info'">
|
|
|
+ {{ configForm.status === 'ENABLED' ? '启用' : '禁用' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="描述">
|
|
|
+ <el-input
|
|
|
+ v-model="configForm.description"
|
|
|
+ type="textarea"
|
|
|
+ :rows="2"
|
|
|
+ disabled
|
|
|
+ />
|
|
|
+ </el-form-item> -->
|
|
|
+ <el-form-item>
|
|
|
+ <el-button
|
|
|
+ type="primary"
|
|
|
+ :loading="configLoading"
|
|
|
+ @click="handleSaveConfig"
|
|
|
+ >
|
|
|
+ 保存配置
|
|
|
+ </el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
<!-- 接入文档 -->
|
|
|
<el-tab-pane label="接入文档" name="docs">
|
|
|
<div class="docs-layout">
|
|
|
@@ -270,7 +377,8 @@
|
|
|
<div class="sidebar-section">
|
|
|
<h3>账户接口</h3>
|
|
|
<ul>
|
|
|
- <li @click="activeSection = 'transfer'" :class="{ active: activeSection === 'transfer' }">转账接口</li>
|
|
|
+ <li @click="activeSection = 'transfer'" :class="{ active: activeSection === 'transfer' }">发起转账</li>
|
|
|
+ <li @click="activeSection = 'transfer_query'" :class="{ active: activeSection === 'transfer_query' }">查询转账</li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
<div class="sidebar-section">
|
|
|
@@ -376,7 +484,7 @@ Signature: {signature}</code></pre>
|
|
|
</div>
|
|
|
|
|
|
<div v-else-if="activeSection === 'transfer'" class="section-content">
|
|
|
- <h2>4. 转账接口</h2>
|
|
|
+ <h2>4. 发起转账</h2>
|
|
|
<h3>4.1 接口说明</h3>
|
|
|
<p>从资金账户转账到支付宝账户/银行卡</p>
|
|
|
|
|
|
@@ -418,6 +526,12 @@ Signature: {signature}</code></pre>
|
|
|
<td>否</td>
|
|
|
<td>转账备注</td>
|
|
|
</tr>
|
|
|
+ <tr>
|
|
|
+ <td>third_biz_no</td>
|
|
|
+ <td>string</td>
|
|
|
+ <td>是</td>
|
|
|
+ <td>三方订单号(商户侧唯一标识,不可重复)</td>
|
|
|
+ </tr>
|
|
|
<tr>
|
|
|
<td>payee_info</td>
|
|
|
<td>object</td>
|
|
|
@@ -536,17 +650,101 @@ Signature: {signature}</code></pre>
|
|
|
"account_book_id": "资金账号",
|
|
|
"amount": 100.00,
|
|
|
"order_title": "转账标题",
|
|
|
+ "third_biz_no": "商户订单号202604270001",
|
|
|
"payee_info": {
|
|
|
- "identity_type": "alipay",
|
|
|
+ "identity_type": "ALIPAY_ACCOUNT",
|
|
|
"name": "收款人姓名",
|
|
|
"identity": "收款人支付宝账号"
|
|
|
}
|
|
|
}'</code></pre>
|
|
|
|
|
|
<h4>响应示例</h4>
|
|
|
- <pre><code>{"code": 200, "message": "转账申请已提交", "data": {"status": "DEALING", "order_no": "2026042711122334455", "fund_order_id": "2026042711122334455"}}</code></pre>
|
|
|
+ <pre><code>{"code": 200, "message": "转账申请已提交", "data": {"status": "DEALING", "order_no": "2026042711122334455", "third_biz_no": "商户订单号202604270001"}}</code></pre>
|
|
|
</div>
|
|
|
|
|
|
+ <div v-else-if="activeSection === 'transfer_query'" class="section-content">
|
|
|
+ <h2>5. 查询转账</h2>
|
|
|
+ <h3>5.1 接口说明</h3>
|
|
|
+ <p>根据三方订单号查询转账状态和详情</p>
|
|
|
+
|
|
|
+ <h4>API接口地址</h4>
|
|
|
+ <p><code>POST https://api.qcsj88888.com/payment/openapi/account/transfer/query</code></p>
|
|
|
+
|
|
|
+ <h4>请求参数</h4>
|
|
|
+ <table class="api-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>参数名</th>
|
|
|
+ <th>类型</th>
|
|
|
+ <th>是否必填</th>
|
|
|
+ <th>描述</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr>
|
|
|
+ <td>third_biz_no</td>
|
|
|
+ <td>string</td>
|
|
|
+ <td>是</td>
|
|
|
+ <td>三方订单号(发起转账时传入的商户侧唯一标识)</td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+
|
|
|
+ <h4>请求示例</h4>
|
|
|
+ <pre><code>curl -X POST 'https://api.qcsj88888.com/payment/openapi/account/transfer/query' \
|
|
|
+ -H 'Authorization: ApiKey your_api_key' \
|
|
|
+ -H 'Signature: your_signature' \
|
|
|
+ -H 'Content-Type: application/json' \
|
|
|
+ -d '{
|
|
|
+ "third_biz_no": "商户订单号202604270001"
|
|
|
+ }'</code></pre>
|
|
|
+
|
|
|
+ <h4>响应示例</h4>
|
|
|
+ <pre><code>{
|
|
|
+ "code": 200,
|
|
|
+ "message": "查询成功",
|
|
|
+ "data": {
|
|
|
+ "status": "SUCCESS",
|
|
|
+ "order_no": "2026042711122334455",
|
|
|
+ "amount": 100.00,
|
|
|
+ "payee_info": {
|
|
|
+ "identity_type": "ALIPAY_ACCOUNT",
|
|
|
+ "name": "张*",
|
|
|
+ "identity": "z****@example.com"
|
|
|
+ },
|
|
|
+ "created_time": "2026-04-27 11:22:33",
|
|
|
+ "updated_time": "2026-04-27 11:25:45"
|
|
|
+ }
|
|
|
+}</code></pre>
|
|
|
+
|
|
|
+ <h4>状态说明</h4>
|
|
|
+ <table class="api-table">
|
|
|
+ <thead>
|
|
|
+ <tr>
|
|
|
+ <th>状态码</th>
|
|
|
+ <th>描述</th>
|
|
|
+ </tr>
|
|
|
+ </thead>
|
|
|
+ <tbody>
|
|
|
+ <tr>
|
|
|
+ <td>DEALING</td>
|
|
|
+ <td>处理中</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>SUCCESS</td>
|
|
|
+ <td>成功</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>FAIL</td>
|
|
|
+ <td>失败</td>
|
|
|
+ </tr>
|
|
|
+ <tr>
|
|
|
+ <td>REFUND</td>
|
|
|
+ <td>已退款</td>
|
|
|
+ </tr>
|
|
|
+ </tbody>
|
|
|
+ </table>
|
|
|
+ </div>
|
|
|
|
|
|
<div v-else-if="activeSection === 'errors'" class="section-content">
|
|
|
<h2>8. 常见错误</h2>
|
|
|
@@ -568,12 +766,13 @@ Signature: {signature}</code></pre>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive } from "vue";
|
|
|
+import { ref, reactive, onMounted } from "vue";
|
|
|
import ApiKeyAPI, {
|
|
|
ApiKeyCreateForm,
|
|
|
ApiKeyPageQuery,
|
|
|
ApiKeyResponse,
|
|
|
} from "@/api/module_payment/apikey";
|
|
|
+import OpenAPI from "@/api/module_payment/openapi";
|
|
|
import CrudToolbarLeft from "@/components/CURD/CrudToolbarLeft.vue";
|
|
|
import CrudToolbarRight from "@/components/CURD/CrudToolbarRight.vue";
|
|
|
import PageSearch from "@/components/CURD/PageSearch.vue";
|
|
|
@@ -582,6 +781,8 @@ import EnhancedDialog from "@/components/CURD/EnhancedDialog.vue";
|
|
|
import { useCrudList } from "@/components/CURD/useCrudList";
|
|
|
import type { IContentConfig, ISearchConfig } from "@/components/CURD/types";
|
|
|
import { ElMessage } from "element-plus";
|
|
|
+import { Loading, CopyDocument } from "@element-plus/icons-vue";
|
|
|
+import { watch } from "vue";
|
|
|
|
|
|
defineOptions({
|
|
|
name: "ApiKey",
|
|
|
@@ -594,11 +795,89 @@ const { searchRef, contentRef, handleQueryClick, handleResetClick, refreshList }
|
|
|
const dataFormRef = ref();
|
|
|
const submitLoading = ref(false);
|
|
|
const apiKeyDetailVisible = ref(false);
|
|
|
+const configFormRef = ref();
|
|
|
+const configLoading = ref(true);
|
|
|
const expandedSections = ref({
|
|
|
payee_info: false,
|
|
|
bankcard_ext_info: false
|
|
|
});
|
|
|
|
|
|
+const configExists = ref(false);
|
|
|
+
|
|
|
+const configForm = reactive({
|
|
|
+ app_id: "",
|
|
|
+ gateway_url: "",
|
|
|
+ notify_url: "",
|
|
|
+ return_url: "",
|
|
|
+ status: "ENABLED",
|
|
|
+ description: "",
|
|
|
+});
|
|
|
+
|
|
|
+watch(activeTab, (newTab) => {
|
|
|
+ if (newTab === "config") {
|
|
|
+ loadOpenConf();
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+async function loadOpenConf() {
|
|
|
+ configLoading.value = true;
|
|
|
+ configExists.value = false;
|
|
|
+ try {
|
|
|
+ const res = await OpenAPI.getOpenConf();
|
|
|
+ if (res.data.code === 0 && res.data.data) {
|
|
|
+ Object.assign(configForm, res.data.data);
|
|
|
+ configExists.value = true;
|
|
|
+ } else {
|
|
|
+ configExists.value = false;
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error("加载配置失败:", error);
|
|
|
+ // 如果返回"配置不存在"错误,则显示创建按钮
|
|
|
+ if (error?.response?.data?.msg?.includes("配置不存在")) {
|
|
|
+ configExists.value = false;
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ configLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleCreateConfig() {
|
|
|
+ configLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await OpenAPI.saveOpenConf({
|
|
|
+ notify_url: "",
|
|
|
+ return_url: "",
|
|
|
+ });
|
|
|
+ if (res.data.code === 0 && res.data.data) {
|
|
|
+ Object.assign(configForm, res.data.data);
|
|
|
+ configExists.value = true;
|
|
|
+ ElMessage.success("创建成功");
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ ElMessage.error("创建失败");
|
|
|
+ } finally {
|
|
|
+ configLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+async function handleSaveConfig() {
|
|
|
+ configLoading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await OpenAPI.saveOpenConf({
|
|
|
+ notify_url: configForm.notify_url,
|
|
|
+ return_url: configForm.return_url,
|
|
|
+ });
|
|
|
+ if (res.data.code === 0 && res.data.data) {
|
|
|
+ Object.assign(configForm, res.data.data);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error(error);
|
|
|
+ } finally {
|
|
|
+ configLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
function toggleExpand(section: string) {
|
|
|
expandedSections.value[section] = !expandedSections.value[section];
|
|
|
}
|
|
|
@@ -608,7 +887,8 @@ function getSectionTitle() {
|
|
|
auth: "认证方式",
|
|
|
signature: "签名验证",
|
|
|
notes: "注意事项",
|
|
|
- transfer: "转账接口",
|
|
|
+ transfer: "发起转账",
|
|
|
+ transfer_query: "查询转账",
|
|
|
errors: "常见错误"
|
|
|
};
|
|
|
return titles[activeSection.value] || "API文档";
|
|
|
@@ -836,6 +1116,38 @@ function downloadApiKeyCsv() {
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
+.config-loading-content {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 60px 0;
|
|
|
+ color: #909399;
|
|
|
+}
|
|
|
+
|
|
|
+.config-loading-content .loading-icon {
|
|
|
+ font-size: 32px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ animation: rotate 1s linear infinite;
|
|
|
+}
|
|
|
+
|
|
|
+@keyframes rotate {
|
|
|
+ from {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+.config-empty {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 40px 0;
|
|
|
+}
|
|
|
+
|
|
|
.docs-layout {
|
|
|
display: flex;
|
|
|
height: calc(100vh - 120px);
|
|
|
@@ -898,6 +1210,14 @@ function downloadApiKeyCsv() {
|
|
|
padding: 0 20px;
|
|
|
}
|
|
|
|
|
|
+.config-layout {
|
|
|
+ padding: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.config-card {
|
|
|
+ max-width: 700px;
|
|
|
+}
|
|
|
+
|
|
|
.section-content {
|
|
|
line-height: 1.6;
|
|
|
padding: 20px 0;
|
|
|
@@ -1031,9 +1351,9 @@ function downloadApiKeyCsv() {
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
padding: 12px 20px;
|
|
|
- border-bottom: 1px solid #e4e7ed;
|
|
|
+ /* border-bottom: 1px solid #e4e7ed; */
|
|
|
margin: -20px -20px 20px -20px;
|
|
|
- background-color: #fafafa;
|
|
|
+ /* background-color: #fafafa; */
|
|
|
}
|
|
|
|
|
|
.card-header span {
|