feat(daxpay): 添加分账交易相关功能

- 新增分账交易 API 接口
- 实现分账订单列表、明细列表和交易信息页面
- 添加分账重试、同步和完结功能
- 优化表格样式和操作功能
This commit is contained in:
DaxPay
2024-11-18 16:51:21 +08:00
parent 8cc6026e44
commit c5701a6747
4 changed files with 589 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
<template>
<basic-drawer
forceRender
v-bind="$attrs"
title="分账订单明细"
width="60%"
:visible="visible"
@close="visible = false"
>
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
keyField="id"
ref="xTable"
:data="records"
:loading="loading"
:cell-style="cellStyle"
>
<vxe-column type="seq" width="60" />
<vxe-column field="receiverNo" title="接收方编号" :min-width="120" />
<vxe-column field="receiverName" title="接收方姓名" :min-width="100" />
<vxe-column field="receiverType" title="接收方类型" :min-width="100">
<template #default="{ row }">
{{ dictConvert('AllocReceiverType', row.receiverType) }}
</template>
</vxe-column>
<vxe-column field="rate" title="分账比例" :min-width="100">
<template #default="{ row }"> {{ row.rate / 100.0 }}% </template>
</vxe-column>
<vxe-column field="amount" title="分账金额" :min-width="100">
<template #default="{ row }"> {{ row.amount / 100.0 }} </template>
</vxe-column>
<vxe-column field="result" title="分账结果" :min-width="130">
<template #default="{ row }"> {{ dictConvert('AllocDetailResult', row.result) }} </template>
</vxe-column>
<vxe-column field="errorMsg" title="错误原因" :min-width="160">
<template #default="{ row }"> {{ row.errorMsg }} </template>
</vxe-column>
<vxe-column field="finishTime" title="完成时间" :min-width="160">
<template #default="{ row }"> {{ row.finishTime }} </template>
</vxe-column>
<vxe-column fixed="right" :min-width="60" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
</span>
</template>
</vxe-column>
</vxe-table>
</basic-drawer>
</template>
<script setup lang="ts">
import { nextTick, ref } from 'vue'
import useTablePage from '@/hooks/bootx/useTablePage'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BasicDrawer from '@/components/Drawer/src/BasicDrawer.vue'
import { useDict } from '@/hooks/bootx/useDict'
import { AllocationOrder, AllocationOrderDetail, detailList } from './Allocation.api'
// 使用hooks
const { loading } = useTablePage(queryPage)
const { dictConvert } = useDict()
let visible = ref(false)
let order = ref<AllocationOrder>({})
let records = ref<AllocationOrderDetail[]>([])
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
const allocationOrderDetailInfo = ref<any>()
nextTick(() => {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
})
/**
* 入口
* @param record
*/
function init(record) {
visible.value = true
order.value = record
queryPage()
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
detailList(order.value.id).then(({ data }) => {
records.value = data
loading.value = false
})
}
/**
* 查看
*/
function show(record) {
allocationOrderDetailInfo.value.init(record)
}
function cellStyle({ row, column }) {
if (column.field == 'status') {
if (row.status == 'success') {
return { color: 'green' }
}
if (row.status == 'fail') {
return { color: 'red' }
}
if (row.status == 'progress') {
return { color: 'orange' }
}
if (row.status == 'close') {
return { color: 'gray' }
}
return { color: 'red' }
}
if (column.field == 'asyncPay') {
if (row.asyncPay) {
return { color: 'green' }
} else {
return { color: 'gray' }
}
}
if (column.field == 'combinationPay') {
if (row.combinationPay) {
return { color: 'green' }
} else {
return { color: 'gray' }
}
}
}
defineExpose({
init,
})
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,270 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query
:query-params="model.queryParam"
:fields="fields"
:default-item-count="3"
@query="queryPage"
@reset="resetQueryParams"
/>
</div>
<div class="m-3 p-3 bg-white">
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
keyField="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="sortChange"
>
<vxe-column type="seq" title="序号" width="60" />
<vxe-column field="allocNo" title="分账单号" :min-width="230">
<template #default="{ row }">
<a @click="show(row)">
{{ row.allocNo }}
</a>
</template>
</vxe-column>
<vxe-column field="title" title="订单标题" :min-width="150" />
<vxe-column field="orderNo" title="支付订单号" :min-width="230">
<template #default="{ row }">
<a @click="showPayOrder(row)">
{{ row.orderNo }}
</a>
</template>
</vxe-column>
<vxe-column field="channel" title="所属通道" :min-width="100">
<template #default="{ row }">
<a-tag>{{ dictConvert('PayChannel', row.channel) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="amount" title="总分账金额(元)" :min-width="120">
<template #default="{ row }">
{{ row.amount ? (row.amount / 100).toFixed(2) : 0 }}
</template>
</vxe-column>
<vxe-column field="status" title="状态" :min-width="120">
<template #default="{ row }">
<a-tag>{{ dictConvert('AllocOrderStatus', row.status) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="result" title="分账结果" :min-width="100">
<template #default="{ row }">
{{ dictConvert('AllocOrderResult', row.result) }}
</template> </vxe-column
><vxe-column field="errorMsg" title="错误原因" :min-width="160" />
<vxe-column field="createTime" title="创建时间" :min-width="160" />
<vxe-column fixed="right" :min-width="200" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
<a-divider type="vertical" />
<a-link @click="showDetail(row)">明细列表</a-link>
<a-divider type="vertical" />
<a-dropdown>
<a>
更多
<icon icon="ant-design:down-outlined" :size="12" />
</a>
<template #overlay>
<a-menu>
<a-menu-item
v-if="
['allocation_processing', 'allocation_end', 'allocation_failed'].includes(
row.status,
)
"
>
<a-link @click="retryInfo(row)">重试</a-link>
</a-menu-item>
<a-menu-item>
<a-link @click="syncInfo(row)">同步</a-link>
</a-menu-item>
<a-menu-item v-if="['allocation_end', 'finish_failed'].includes(row.status)">
<a-link @click="finishInfo(row)">完结</a-link>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</vxe-column>
</vxe-table>
<vxe-pager
size="medium"
:loading="loading"
:current-page="pagination.current"
:page-size="pagination.size"
:total="pagination.total"
@page-change="handleTableChange"
/>
</div>
<AllocDetailList ref="allocDetailList" />
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { page, sync, finish, retry } from './Allocation.api'
import useTablePage from '@/hooks/bootx/useTablePage'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { useMessage } from '@/hooks/web/useMessage'
import { useDict } from '@/hooks/bootx/useDict'
import ALink from '/@/components/Link/Link.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { LIST, QueryField, STRING } from '@/components/Bootx/Query/Query'
import { LabeledValue } from 'ant-design-vue/lib/select'
import { FormEditType } from '@/enums/formTypeEnum'
import AllocDetailList from './AllocDetailList.vue'
import { Icon } from '@/components/Icon'
// 使用hooks
const {
handleTableChange,
pageQueryResHandel,
resetQueryParams,
pagination,
sortChange,
sortParam,
pages,
model,
loading,
} = useTablePage(queryPage)
const { createMessage, createConfirm } = useMessage()
const { dictConvert } = useDict()
const xTable = ref<VxeTableInstance>()
const xToolbar = ref<VxeToolbarInstance>()
const allocDetailList = ref<any>()
const allocationOrderInfo = ref<any>()
let payChannelList = ref<LabeledValue[]>([])
const payOrderInfo = ref<any>()
const fields = computed(() => {
return [
{
field: 'channel',
type: LIST,
name: '分账通道',
placeholder: '请选择分账通道',
selectList: payChannelList.value,
},
{ field: 'orderNo', type: STRING, name: '分账订单号', placeholder: '请输入分账订单号' },
{ field: 'paymentId', type: STRING, name: '支付订单ID', placeholder: '请输入支付订单ID' },
{ field: 'title', type: STRING, name: '支付订单标题', placeholder: '请输入支付订单标题' },
{ field: 'allocNo', type: STRING, name: '分账业务号', placeholder: '请输入分账业务号' },
] as QueryField[]
})
onMounted(() => {
vxeBind()
initData()
queryPage()
})
/**
* 绑定
*/
function vxeBind() {
xTable.value?.connect(xToolbar.value as VxeToolbarInstance)
}
/**
* 初始化数据
*/
async function initData() {
// findChannels().then(({ data }) => (payChannelList.value = data))
}
/**
* 查看
*/
function show(record) {
allocationOrderInfo.value.init(record.allocNo, FormEditType.Show)
}
/**
* 查询分账明细列表
* @param record
*/
function showDetail(record) {
allocDetailList.value.init(record)
}
/**
* 查看支付单信息
*/
function showPayOrder(record) {
payOrderInfo.value.init(record.orderNo)
}
/**
* 同步分账状态
* @param record
*/
function syncInfo(record) {
createConfirm({
iconType: 'info',
title: '同步分账状态',
content: '确定同步分账状态吗?',
onOk: () => {
sync(record.allocNo).then(() => {
createMessage.success('同步成功')
queryPage()
})
},
})
}
/**
* 分账重试
*/
function retryInfo(record) {
createConfirm({
iconType: 'info',
title: '分账重试',
content: '确定分账重试吗?',
onOk: () => {
retry(record.bizAllocNo).then(() => {
createMessage.success('分账重试请求发送成功')
queryPage()
})
},
})
}
/**
* 完结分账
*/
function finishInfo(record) {
createConfirm({
iconType: 'info',
title: '完结分账',
content: '确定完结分账吗?',
onOk: () => {
finish(record.allocNo).then(() => {
createMessage.success('完结请求发送成功')
queryPage()
})
},
})
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
...sortParam,
}).then(({ data }) => {
pageQueryResHandel(data)
})
return Promise.resolve()
}
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,171 @@
import { defHttp } from '@/utils/http/axios'
import { PageResult, Result } from '#/axios'
import { MchEntity } from '#/web'
/**
* 分页
*/
export function page(params) {
return defHttp.get<Result<PageResult<AllocationOrder>>>({
url: '/allocation/transaction/page',
params,
})
}
/**
* 查询详情
*/
export function get(id) {
return defHttp.get<Result<AllocationOrder>>({
url: '/allocation/transaction/findById',
params: { id },
})
}
/**
* 获取订单详细信息
*/
export function getOrderByAllocNo(allocNo: string) {
return defHttp.get<Result>({
url: '/allocation/transaction/findByAllocNo',
params: { allocNo },
})
}
/**
* 扩展信息
*/
export function getExtra(id) {
return defHttp.get<Result<AllocationOrder>>({
url: '/allocation/transaction/findById',
params: { id },
})
}
/**
* 明细列表
*/
export function detailList(orderId) {
return defHttp.get<Result<AllocationOrderDetail[]>>({
url: '/allocation/transaction/detail/findAll',
params: { orderId },
})
}
/**
* 明细详情
*/
export function detail(id) {
return defHttp.get<Result<AllocationOrderDetail>>({
url: '/allocation/transaction/detail/findById',
params: { id },
})
}
/**
* 分账完结
*/
export function finish(allocNo) {
return defHttp.post<Result<AllocationOrder>>({
url: '/allocation/transaction/finish',
params: { allocNo },
})
}
/**
* 分账完结
*/
export function retry(bizAllocNo) {
return defHttp.post<Result<AllocationOrder>>({
url: '/allocation/transaction/retry',
params: { bizAllocNo },
})
}
/**
* 查询分账结果
*/
export function sync(allocNo) {
return defHttp.post<Result<AllocationOrder>>({
url: '/allocation/transaction/sync',
params: { allocNo },
})
}
/**
* 分账订单
*/
export interface AllocationOrder extends MchEntity {
// 分账单号
allocNo?: string
// 商户分账单号
bizAllocNo?: string
// 通道分账号
outAllocNo?: string
// 支付订单ID
orderId?: string
// 支付订单号
orderNo?: string
// 商户支付订单号
bizOrderNo?: string
// 外部系统支付订单号
outOrderNo?: string
// 支付订单标题
title?: string
// 所属通道
channel?: string
// 总分账金额
amount?: number
// 分账描述
description?: string
// 状态
status?: string
// 处理结果
result?: string
// 完成时间
finishTime?: string
// 异步通知地址
notifyUrl?: string
// 商户扩展参数
attach?: string
// 附加参数
extraParam?: string
// 请求时间
reqTime?: string
// 支付终端ip
clientIp?: string
// 错误码
errorCode?: string
// 错误原因
errorMsg?: string
}
/**
* 分账订单明细
*/
export interface AllocationOrderDetail extends MchEntity {
// 分账订单ID
orderId?: string
// 分账明细单号
receiverId?: string
// 比例
rate: number
// 金额
amount: number
// 分账接收方类型
receiverType?: string
// 接收方账号
receiverAccount?: string
// 接收方姓名
receiverName?: string
// 接收方编号
receiverNo?: string
// 分账结果
result?: string
// 错误代码
errorCode?: string
// 错误原因
errorMsg?: string
// 完成时间
finishTime?: string
}