14 Commits
2.0.1 ... 2.0.3

Author SHA1 Message Date
xxm1995
a62f930de5 fix 描述问题错误 2024-03-14 20:25:08 +08:00
xxm1995
29cc17635a perf 优化各类流水的查询条件 2024-03-14 17:56:58 +08:00
xxm1995
4d7fbdc71f feat 云闪付支付记录 2024-03-13 17:46:31 +08:00
xxm1995
5beac2939e fix 消息通知列表查看订单信息失败 2024-03-11 18:48:01 +08:00
xxm1995
8e23238c98 doc 更新用户协议和开源协议 2024-03-11 13:28:35 +08:00
bootx
b952affe49 feat 云闪付配置界面, 修复useUpload根路径拼接问题 2024-03-10 22:51:26 +08:00
bootx
966b876ecc feat 退款重试 2024-03-06 23:24:19 +08:00
bootx
5a1c056132 fix 显示字段调整 2024-03-05 23:24:10 +08:00
xxm1995
f2df62584d fix 订单时间字段显示为空 2024-03-05 18:30:09 +08:00
xxm1995
97466ee5e7 feat 对账明细和差异单查询条件 2024-03-05 16:51:39 +08:00
bootx
052af82748 feat 对账差异单和定时任务开发 2024-03-04 23:03:53 +08:00
bootx
e3061291e7 feat 对账订单 2024-03-03 23:20:54 +08:00
xxm1995
28456e9fbd feat 流水表字段调整, 对账功能联调 2024-02-28 17:02:01 +08:00
bootx
73183f0509 fix 查询条件问题 2024-02-27 23:46:07 +08:00
42 changed files with 1379 additions and 144 deletions

View File

@@ -7,7 +7,7 @@ VITE_PUBLIC_PATH=/
# 接口前缀
VITE_GLOB_API_URL_PREFIX=/server
# 接口地址留空即可
# 接口地址
VITE_GLOB_API_URL=
# 跨域代理,您可以配置多个 ,请注意,没有换行符
@@ -19,5 +19,5 @@ VITE_DROP_CONSOLE=false
# 接口超时时间
VITE_GLOB_API_TIMEOUT=30000
# 文件上传地址
# 文件上传地址 未使用
VITE_GLOB_UPLOAD_URL=/upload

View File

@@ -4,7 +4,7 @@ VITE_PUBLIC_PATH=/
# 接口前缀
VITE_GLOB_API_URL_PREFIX=/server
# 接口地址留空即可
# 接口地址
VITE_GLOB_API_URL=
# 控制台不输出console
@@ -21,7 +21,7 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE=false
# 接口超时时间
VITE_GLOB_API_TIMEOUT=30000
# 文件上传地址
# 文件上传地址 未使用
VITE_GLOB_UPLOAD_URL=/upload

View File

@@ -186,7 +186,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright (c) 2021 bootx Authors. All Rights Reserved.
Copyright (c) 2022 济南易杯光年软件技术有限公司 Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,11 @@
# Dax-Pay-UI
## ❗使用须知
`DaxPay`是一款基于`Apache License 2.0`协议分发的开源软件,受中华人民共和国相关法律法规的保护和限制,可以在符合[《用户授权使用协议》](用户授权使用协议.txt)和
[《Apache License 2.0》](LICENSE)开源协议情况下进行免费使用、学习和交流。**在使用前请阅读上述协议,如果不同意请勿进行使用。**
## 🍈项目介绍
> DaxPay是一套基于Bootx-Platform脚手架构建的一套开源支付网关系统已经对接支付宝、微信支付相关的接口以及扩展了钱包支付、储值卡支付、现金支付等新的支付方式。

View File

@@ -2,7 +2,7 @@ import { useUserStoreWithOut } from '/@/store/modules/user'
import { computed } from 'vue'
import { getAppEnvConfig } from '/@/utils/env'
const useUserStore = useUserStoreWithOut()
const { VITE_GLOB_API_URL } = getAppEnvConfig()
const { VITE_GLOB_API_URL, VITE_GLOB_API_URL_PREFIX } = getAppEnvConfig()
export function useUpload(uploadUrl: string) {
/**
@@ -18,7 +18,7 @@ export function useUpload(uploadUrl: string) {
* 上传地址
*/
const uploadAction = computed(() => {
return VITE_GLOB_API_URL + uploadUrl
return VITE_GLOB_API_URL + VITE_GLOB_API_URL_PREFIX + uploadUrl
})
return {
tokenHeader,

View File

@@ -1,11 +1,11 @@
// github repo url
export const GITHUB_URL = 'https://github.com/xxm1995/bootx-platform'
export const GITHUB_URL = 'https://github.com/dromara/dax-pay'
// gitee repo url
export const GITEE_URL = 'https://gitee.com/bootx/bootx-platform'
export const GITEE_URL = 'https://gitee.com/dromara/dax-pay'
// 文档地址
export const DOC_URL = 'https://gitee.com/bootx/bootx-platform'
export const DOC_URL = 'https://bootx.gitee.io/'
// 预览地址
export const SITE_URL = 'http://v3.platform.bootx.cn/'
export const SITE_URL = 'https://daxpay.demo.bootx.cn/'

View File

@@ -16,10 +16,10 @@
<div class="my-auto">
<img :alt="title" src="../../assets/svg/login-box-bg.svg" class="w-1/2 -mt-16 -enter-x" />
<div class="mt-10 font-medium text-white -enter-x">
<span class="inline-block mt-4 text-3xl"> 开箱即用的中后台管理系统</span>
<span class="inline-block mt-4 text-3xl"> 开箱即用的支付网关系统</span>
</div>
<div class="mt-5 font-normal text-white dark:text-gray-500 -enter-x"
>基于Vite+Vue3打造支持支付收单工作流(Flowable)三方对接等模块
>基于Vite+Vue3打造支持支付微信支付云闪付等支付方式的统一网关系统
</div>
</div>
</div>

View File

@@ -38,4 +38,6 @@ export interface AlipayRecord extends BaseEntity {
gatewayOrderNo?: string
// 终端ip
ip?: string
// 网关时间
gatewayTime?: string
}

View File

@@ -25,6 +25,9 @@
<a-descriptions-item label="业务类型">
<a-tag>{{ dictConvert('AlipayRecordType', alipayRecord.type) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="网关时间">
{{ alipayRecord.gatewayTime }}
</a-descriptions-item>
<a-descriptions-item label="记录时间">
{{ alipayRecord.createTime }}
</a-descriptions-item>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query :query-params="model.queryParam" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
<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 }" />
@@ -23,7 +23,7 @@
<vxe-column field="amount" title="金额" />
<vxe-column field="orderId" title="本地订单ID" width="170" />
<vxe-column field="gatewayOrderNo" title="网关订单号" width="170" />
<vxe-column field="createTime" title="记录时间" sortable />
<vxe-column field="gatewayTime" title="网关时间" sortable />
<vxe-column fixed="right" width="50" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
@@ -44,7 +44,7 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { computed, onMounted } from 'vue'
import { $ref } from 'vue/macros'
import { page } from './AlipayRecord.api'
import useTablePage from '/@/hooks/bootx/useTablePage'
@@ -54,7 +54,8 @@
import AlipayRecordInfo from './AlipayRecordInfo.vue'
import ALink from '/@/components/Link/Link.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { STRING } from '/@/components/Bootx/Query/Query'
import { LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import { LabeledValue } from 'ant-design-vue/lib/select'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, sortChange, sortParam, pages, model, loading } =
@@ -62,18 +63,23 @@
const { notification, createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
let alipayRecordInfo = $ref<any>()
const alipayRecordInfo = $ref<any>()
const xTable = $ref<VxeTableInstance>()
const xToolbar = $ref<VxeToolbarInstance>()
const fields = [
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },
{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
]
let recordTypeList = $ref<LabeledValue[]>([])
const fields = computed(() => {
return [
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },
{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' },
{ field: 'type', type: LIST, name: '记录类型', placeholder: '请选择记录类型', selectList: recordTypeList },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
] as QueryField[]
})
onMounted(() => {
vxeBind()
initData()
queryPage()
})
@@ -84,6 +90,13 @@
xTable?.connect(xToolbar as VxeToolbarInstance)
}
/**
* 初始化数据
*/
async function initData() {
recordTypeList = await dictDropDown('AlipayRecordType')
}
/**
* 查看
*/

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query :query-params="model.queryParam" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
<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 }" />
@@ -53,9 +53,10 @@
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { useMessage } from '/@/hooks/web/useMessage'
import { QueryField, STRING } from '/@/components/Bootx/Query/Query'
import { LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import { useDict } from '/@/hooks/bootx/useDict'
import CashRecordInfo from './CashRecordInfo.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, sortChange, sortParam, pages, model, loading } =
@@ -63,23 +64,41 @@
const { notification, createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
// 查询条件
const fields = computed(() => {
return [{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' }] as QueryField[]
})
const xTable = $ref<VxeTableInstance>()
const xToolbar = $ref<VxeToolbarInstance>()
const cashRecordInfo = $ref<any>()
let recordTypeList = $ref<LabeledValue[]>([])
// 查询条件
const fields = computed(() => {
return [
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },
{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' },
{ field: 'type', type: LIST, name: '记录类型', placeholder: '请选择记录类型', selectList: recordTypeList },
] as QueryField[]
})
onMounted(() => {
vxeBind()
initData()
queryPage()
})
/**
* 绑定
*/
function vxeBind() {
xTable?.connect(xToolbar as VxeToolbarInstance)
}
/**
* 初始化数据
*/
async function initData() {
recordTypeList = await dictDropDown('CashRecordType')
}
/**
* 分页查询
*/

View File

@@ -1,6 +1,7 @@
<template>
<alipay-config-edit ref="alipay" @ok="ok" />
<wechat-pay-config-edit ref="wechat" @ok="ok" />
<union-pay-config-edit ref="union" @ok="ok" />
<wallet-config-edit ref="wallet" @ok="ok" />
<voucher-config-edit ref="voucher" @ok="ok" />
<cash-config-edit ref="cash" @ok="ok" />
@@ -15,11 +16,13 @@
import WalletConfigEdit from '/@/views/payment/channel/wallet/config/WalletConfigEdit.vue'
import VoucherConfigEdit from '/@/views/payment/channel/voucher/config/VoucherConfigEdit.vue'
import CashConfigEdit from '/@/views/payment/channel/cash/config/CashConfigEdit.vue'
import UnionPayConfigEdit from '/@/views/payment/channel/union/config/UnionPayConfigEdit.vue'
const { createMessage } = useMessage()
let alipay = $ref<any>()
let wechat = $ref<any>()
let union = $ref<any>()
let wallet = $ref<any>()
let voucher = $ref<any>()
let cash = $ref<any>()
@@ -38,6 +41,10 @@
wechat.init(record)
break
}
case payChannelEnum.UNION_PAY: {
union.init(record)
break
}
case payChannelEnum.WALLET: {
wallet.init(record)
break

View File

@@ -0,0 +1,63 @@
import { defHttp } from '/@/utils/http/axios'
import { Result } from '/#/axios'
import { BaseEntity, KeyValue } from '/#/web'
/**
* 获取单条
*/
export function getConfig() {
return defHttp.get<Result<UnionPayConfig>>({
url: '/union/pay/config/getConfig',
})
}
/**
* 更新
*/
export function update(obj: UnionPayConfig) {
return defHttp.post({
url: '/union/pay/config/update',
data: obj,
})
}
/**
* 获取云闪付支持支付方式
*/
export function findPayWayList() {
return defHttp.get<Result<KeyValue[]>>({
url: '/union/pay/config/findPayWays',
})
}
/**
* 微信支付配置
*/
export interface UnionPayConfig extends BaseEntity {
// 商户号
machId?: string
// 商户收款账号
seller?: string
// 是否启用
enable: boolean
// 签名类型
signType?: string
// 是否为证书签名
certSign?: boolean
// 应用私钥证书
keyPrivateCert?: string | null
// 私钥证书对应的密码
keyPrivateCertPwd?: string | null
// 中级证书
acpMiddleCert?: string | null
// 根证书
acpRootCert?: string | null
// 异步通知页面路径
notifyUrl?: string
// 页面跳转同步通知页面路径
returnUrl?: string
// 是否沙箱环境
sandbox?: boolean
// 支持的支付类型
payWays?: string[]
}

View File

@@ -0,0 +1,273 @@
<template>
<basic-drawer
showFooter
v-bind="$attrs"
width="60%"
title="云闪付支付配置"
:visible="visible"
:maskClosable="false"
@close="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form
class="small-from-item"
ref="formRef"
:model="form"
:rules="rules"
:validate-trigger="['blur', 'change']"
:label-col="labelCol"
:wrapper-col="wrapperCol"
>
<a-form-item label="主键" name="id" :hidden="true">
<a-input v-model:value="form.id" :disabled="showable" />
</a-form-item>
<a-form-item label="商户号" name="machId">
<a-input v-model:value="form.machId" :disabled="showable" placeholder="请输入商户号" />
</a-form-item>
<a-form-item label="是否启用" name="enable">
<a-switch checked-children="启用" un-checked-children="停用" v-model:checked="form.enable" />
</a-form-item>
<a-form-item label="签名类型" name="signType">
<a-select
allowClear
:disabled="showable"
:options="signTypeList"
v-model:value="form.signType"
style="width: 100%"
placeholder="选择支付方式"
/>
</a-form-item>
<a-form-item name="keyPrivateCert" label="应用私钥证书">
<a-upload
v-if="!form.keyPrivateCert"
:disabled="showable"
name="file"
:multiple="false"
:action="uploadAction"
:headers="tokenHeader"
:showUploadList="false"
@change="(info) => handleChange(info, 'keyPrivateCert')"
>
<a-button type="primary" preIcon="carbon:cloud-upload"> 证书上传 </a-button>
</a-upload>
<a-input v-else defaultValue="acp_enc.cer" disabled>
<template #addonAfter v-if="!showable">
<a-tooltip>
<template #title> 删除上传的证书文件 </template>
<icon @click="form.keyPrivateCert = ''" icon="ant-design:close-circle-outlined" :size="20" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item label="私钥证书密码" name="keyPrivateCertPwd">
<a-input allow-clear v-model:value="form.keyPrivateCertPwd" :disabled="showable" placeholder="请输入私钥证书密码" />
</a-form-item>
<a-form-item name="acpMiddleCert" label="中级证书">
<a-upload
v-if="!form.acpMiddleCert"
:disabled="showable"
name="file"
:multiple="false"
:action="uploadAction"
:headers="tokenHeader"
:showUploadList="false"
@change="(info) => handleChange(info, 'acpMiddleCert')"
>
<a-button type="primary" preIcon="carbon:cloud-upload"> 证书上传 </a-button>
</a-upload>
<a-input v-else defaultValue="acp_middle.cer" disabled>
<template #addonAfter v-if="!showable">
<a-tooltip>
<template #title> 删除上传的证书文件 </template>
<icon @click="form.acpMiddleCert = ''" icon="ant-design:close-circle-outlined" :size="20" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item name="acpRootCert" label="根证书">
<a-upload
v-if="!form.acpRootCert"
:disabled="showable"
name="file"
:multiple="false"
:action="uploadAction"
:headers="tokenHeader"
:showUploadList="false"
@change="(info) => handleChange(info, 'acpRootCert')"
>
<a-button type="primary" preIcon="carbon:cloud-upload"> 证书上传 </a-button>
</a-upload>
<a-input v-else defaultValue="acp_root.cer" disabled>
<template #addonAfter v-if="!showable">
<a-tooltip>
<template #title> 删除上传的证书文件 </template>
<icon @click="form.acpRootCert = ''" icon="ant-design:close-circle-outlined" :size="20" />
</a-tooltip>
</template>
</a-input>
</a-form-item>
<a-form-item name="notifyUrl">
<template #label>
<basic-title helpMessage="此处为本网关接收通知的地址, 而不是客户系统接收通知所需的地址"> 异步通知地址 </basic-title>
</template>
<a-input v-model:value="form.notifyUrl" :disabled="showable" placeholder="请输入服务器异步通知地址" />
</a-form-item>
<a-form-item name="returnUrl">
<template #label>
<basic-title helpMessage="此处为本网关接收通知的地址, 而不是客户系统接收通知所需的地址"> 同步通知地址 </basic-title>
</template>
<a-input v-model:value="form.returnUrl" :disabled="showable" placeholder="请输入页面跳转同步通知地址" />
</a-form-item>
<a-form-item label="支持支付方式" name="payWays">
<a-select
allowClear
mode="multiple"
:disabled="showable"
:options="payWayList"
v-model:value="form.payWays"
style="width: 100%"
placeholder="选择支付方式"
/>
</a-form-item>
<a-form-item label="测试环境" name="sandbox">
<a-switch checked-children="" un-checked-children="" v-model:checked="form.sandbox" />
</a-form-item>
<a-form-item label="备注" name="remark">
<a-textarea v-model:value="form.remark" :disabled="showable" placeholder="请输入备注" />
</a-form-item>
</a-form>
</a-spin>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
<a-button v-if="!showable" key="forward" :loading="confirmLoading" type="primary" @click="handleOk">保存</a-button>
</a-space>
</template>
</basic-drawer>
</template>
<script lang="ts" setup>
import { computed, nextTick } from 'vue'
import { $ref } from 'vue/macros'
import useFormEdit from '/@/hooks/bootx/useFormEdit'
import { FormInstance, Rule } from 'ant-design-vue/lib/form'
import { BasicDrawer } from '/@/components/Drawer'
import Icon from '/@/components/Icon/src/Icon.vue'
import { useUpload } from '/@/hooks/bootx/useUpload'
import { useMessage } from '/@/hooks/web/useMessage'
import { LabeledValue } from 'ant-design-vue/lib/select'
import BasicTitle from '/@/components/Basic/src/BasicTitle.vue'
import { getConfig, update, findPayWayList, UnionPayConfig } from './UnionPayConfig.api'
import { useDict } from '/@/hooks/bootx/useDict'
const { handleCancel, search, diffForm, labelCol, wrapperCol, modalWidth, title, confirmLoading, visible, editable, showable } =
useFormEdit()
// 文件上传
const { tokenHeader, uploadAction } = useUpload('/union/pay/config/toBase64')
const { createMessage } = useMessage()
const { dictDropDown } = useDict()
// 表单
const formRef = $ref<FormInstance>()
let rawForm: any
let form = $ref<UnionPayConfig>({
id: null,
seller: '',
enable: false,
notifyUrl: '',
returnUrl: '',
payWays: [],
sandbox: false,
})
// 校验
const rules = computed(() => {
return {
machId: [{ required: true, message: '请输入商户号' }],
wxAppId: [{ required: true, message: '请输入应用编号' }],
// certSign: [{ required: true, message: '请选择是否为证书签名' }],
signType: [{ required: true, message: '请选择签名类型' }],
keyPrivateCert: [{ required: true, message: '请上传应用私钥证书' }],
keyPrivateCertPwd: [{ required: true, message: '请输入应用私钥证书密码' }],
acpMiddleCert: [{ required: true, message: '请上传中级证书' }],
acpRootCert: [{ required: true, message: '请上传根证书' }],
enable: [{ required: true, message: '请选择是否启用' }],
notifyUrl: [{ required: true, message: '请输入异步通知页面地址' }],
returnUrl: [{ required: true, message: '请输入同步通知页面地址' }],
apiVersion: [{ required: true, message: '请选择支付API版本' }],
apiKeyV2: [{ required: true, message: '请输入V2秘钥' }],
sandbox: [{ required: true, message: '请选择是否为沙箱环境' }],
payWays: [{ required: true, message: '请选择支持的支付类型' }],
} as Record<string, Rule[]>
})
let payWayList = $ref<LabeledValue[]>([])
let signTypeList = $ref<LabeledValue[]>([])
// 事件
const emits = defineEmits(['ok'])
/**
* 入口
*/
function init() {
visible.value = true
resetForm()
getInfo()
}
// 获取信息
async function getInfo() {
confirmLoading.value = true
findPayWayList().then(({ data }) => {
payWayList = data
})
getConfig().then(({ data }) => {
rawForm = { ...data }
form = data
confirmLoading.value = false
})
signTypeList = await dictDropDown('UnionPaySignType')
}
// 保存
function handleOk() {
formRef?.validate().then(async () => {
confirmLoading.value = true
await update({
...form,
...diffForm(rawForm, form, 'keyPrivateCert', 'keyPrivateCertPwd', 'acpMiddleCert', 'acpRootCert'),
}).finally(() => {
confirmLoading.value = false
})
handleCancel()
createMessage.success('保存成功')
emits('ok')
})
}
// 重置表单
function resetForm() {
nextTick(() => {
formRef?.resetFields()
})
}
/**
* 文件上传
*/
function handleChange(info, fieldName) {
// 上传完毕
if (info.file.status === 'done') {
const res = info.file.response
if (!res.code) {
form[fieldName] = res.data
createMessage.success(`${info.file.name} 上传成功!`)
} else {
createMessage.error(`${res.msg}`)
}
} else if (info.file.status === 'error') {
createMessage.error('上传失败')
}
}
defineExpose({
init,
})
</script>
<style lang="less" scoped></style>

View File

@@ -0,0 +1,43 @@
import { defHttp } from '/@/utils/http/axios'
import { Result, PageResult } from '/#/axios'
import { BaseEntity } from '/#/web'
/**
* 分页
*/
export function page(params) {
return defHttp.get<Result<PageResult<UnionPayRecord>>>({
url: '/union/pay/record/page',
params,
})
}
/**
* 获取详情
*/
export function get(id) {
return defHttp.get<Result<UnionPayRecord>>({
url: '/union/pay/record/findById',
params: { id },
})
}
/**
* 记录
*/
export interface UnionPayRecord extends BaseEntity {
// 标题
title?: string
// 业务类型
type?: string
// 金额
amount?: string
// 交易订单号
orderId?: string
// 交易订单号
gatewayOrderNo?: string
// 终端ip
ip?: string
// 网关时间
gatewayTime?: string
}

View File

@@ -0,0 +1,90 @@
<template>
<basic-modal
title="查看"
v-bind="$attrs"
:loading="confirmLoading"
:width="modalWidth"
:visible="visible"
:mask-closable="showable"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-descriptions bordered title="" :column="{ md: 1, sm: 1, xs: 1 }">
<a-descriptions-item label="标题">
{{ unionPayRecord.title }}
</a-descriptions-item>
<a-descriptions-item label="金额(分)">
{{ unionPayRecord.amount }}
</a-descriptions-item>
<a-descriptions-item label="本地订单号">
{{ unionPayRecord.orderId }}
</a-descriptions-item>
<a-descriptions-item label="网关订单号">
{{ unionPayRecord.gatewayOrderNo }}
</a-descriptions-item>
<a-descriptions-item label="业务类型">
<a-tag>{{ dictConvert('UnionPayRecordType', unionPayRecord.type) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="网关时间">
{{ unionPayRecord.gatewayTime }}
</a-descriptions-item>
<a-descriptions-item label="记录时间">
{{ unionPayRecord.createTime }}
</a-descriptions-item>
</a-descriptions>
</a-spin>
<template #footer>
<a-button key="cancel" @click="handleCancel">取消</a-button>
</template>
</basic-modal>
</template>
<script setup lang="ts">
import { get, UnionPayRecord } from './UnionPayRecord.api'
import { $ref } from 'vue/macros'
import { BasicModal } from '/@/components/Modal'
import useFormEdit from '/@/hooks/bootx/useFormEdit'
import { useDict } from '/@/hooks/bootx/useDict'
const {
initFormEditType,
handleCancel,
search,
labelCol,
wrapperCol,
modalWidth,
title,
confirmLoading,
visible,
editable,
showable,
formEditType,
} = useFormEdit()
const { dictConvert } = useDict()
let loading = $ref(false)
let unionPayRecord = $ref<UnionPayRecord>({})
/**
* 入口
*/
function init(record: UnionPayRecord) {
visible.value = true
unionPayRecord = record
initData(record.id as string)
}
/**
* 初始化数据
*/
async function initData(alipayId) {
loading = true
const { data } = await get(alipayId)
loading = false
unionPayRecord = data
}
defineExpose({ init })
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,121 @@
<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
row-id="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="title" title="标题" />
<vxe-column field="type" title="类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('UnionPayRecordType', row.type) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="amount" title="金额" />
<vxe-column field="orderId" title="本地订单ID" width="170" />
<vxe-column field="gatewayOrderNo" title="网关订单号" width="170" />
<vxe-column field="gatewayTime" title="网关时间" sortable />
<vxe-column fixed="right" width="50" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
</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"
/>
<union-pay-record-info ref="unionPayRecordInfo" />
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { $ref } from 'vue/macros'
import { page } from './UnionPayRecord.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, STRING } from '/@/components/Bootx/Query/Query'
import UnionPayRecordInfo from './UnionPayRecordInfo.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, sortChange, sortParam, pages, model, loading } =
useTablePage(queryPage)
const { notification, createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
let unionPayRecordInfo = $ref<any>()
const xTable = $ref<VxeTableInstance>()
const xToolbar = $ref<VxeToolbarInstance>()
let recordTypeList = $ref<LabeledValue[]>([])
const fields = [
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },
{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' },
{ field: 'type', type: LIST, name: '记录类型', placeholder: '请选择记录类型', selectList: recordTypeList },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
]
onMounted(() => {
vxeBind()
initData()
queryPage()
})
/**
* 绑定
*/
function vxeBind() {
xTable?.connect(xToolbar as VxeToolbarInstance)
}
/**
* 初始化数据
*/
async function initData() {
recordTypeList = await dictDropDown('UnionPayRecordType')
}
/**
* 查看
*/
function show(record) {
unionPayRecordInfo.init(record)
}
/**
* 分页查询
*/
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

@@ -23,7 +23,7 @@
<a-input v-model:value="form.appSecret" :disabled="showable" placeholder="APPID对应的接口密码用于获取接口调用凭证时使用" />
</a-form-item>
<a-form-item label="是否启用" name="enable">
<a-switch v-model:checked="form.enable" />
<a-switch checked-children="启用" un-checked-children="停用" v-model:checked="form.enable" />
</a-form-item>
<a-form-item name="notifyUrl">
<template #label>

View File

@@ -38,4 +38,6 @@ export interface WechatPayRecord extends BaseEntity {
gatewayOrderNo?: string
// 终端ip
ip?: string
// 网关时间
gatewayTime?: string
}

View File

@@ -25,6 +25,9 @@
<a-descriptions-item label="业务类型">
<a-tag>{{ dictConvert('WechatPayRecordType', wechatPayRecord.type) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="网关时间">
{{ wechatPayRecord.gatewayTime }}
</a-descriptions-item>
<a-descriptions-item label="记录时间">
{{ wechatPayRecord.createTime }}
</a-descriptions-item>

View File

@@ -1,7 +1,7 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query :query-params="model.queryParam" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
<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 }" />
@@ -23,7 +23,7 @@
<vxe-column field="amount" title="金额" />
<vxe-column field="orderId" title="本地订单ID" width="170" />
<vxe-column field="gatewayOrderNo" title="网关订单号" width="170" />
<vxe-column field="createTime" title="记录时间" sortable />
<vxe-column field="gatewayTime" title="网关时间" sortable />
<vxe-column fixed="right" width="50" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
@@ -44,9 +44,9 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { computed, onMounted } from 'vue'
import { $ref } from 'vue/macros'
import { page, WechatPayRecord } from './WechatPayRecord.api'
import { page } from './WechatPayRecord.api'
import useTablePage from '/@/hooks/bootx/useTablePage'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { useMessage } from '/@/hooks/web/useMessage'
@@ -54,7 +54,8 @@
import WechatPayRecordInfo from './WechatPayRecordInfo.vue'
import ALink from '/@/components/Link/Link.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { STRING } from '/@/components/Bootx/Query/Query'
import { LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import { LabeledValue } from 'ant-design-vue/lib/select'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, sortChange, sortParam, pages, model, loading } =
@@ -62,18 +63,23 @@
const { notification, createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
let wechatPayRecordInfo = $ref<any>()
const xTable = $ref<VxeTableInstance>()
const xToolbar = $ref<VxeToolbarInstance>()
const wechatPayRecordInfo = $ref<any>()
const fields = [
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },
{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
]
let recordTypeList = $ref<LabeledValue[]>([])
const fields = computed(() => {
return [
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },
{ field: 'orderId', type: STRING, name: '本地订单ID', placeholder: '请输入完整本地订单ID' },
{ field: 'type', type: LIST, name: '记录类型', placeholder: '请选择记录类型', selectList: recordTypeList },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
] as QueryField[]
})
onMounted(() => {
vxeBind()
initData()
queryPage()
})
@@ -83,6 +89,12 @@
function vxeBind() {
xTable?.connect(xToolbar as VxeToolbarInstance)
}
/**
* 初始化数据
*/
async function initData() {
recordTypeList = await dictDropDown('WechatPayRecordType')
}
/**
* 查看

View File

@@ -110,7 +110,7 @@
// 查询条件
const fields = computed(() => {
return [
{ field: 'paymentId', type: STRING, name: '支付ID', placeholder: '请输入完整支付ID' },
{ field: 'id', type: STRING, name: '支付ID', placeholder: '请输入完整支付ID' },
{ field: 'businessNo', type: STRING, name: '业务号', placeholder: '请输入业务号' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
{ field: 'title', type: STRING, name: '标题', placeholder: '请输入标题' },

View File

@@ -0,0 +1,43 @@
import { defHttp } from '/@/utils/http/axios'
import { PageResult, Result } from '/#/axios'
import { BaseEntity } from '/#/web'
/**
* 明细分页
*/
export function page(params) {
return defHttp.get<Result<PageResult<ReconcileDetail>>>({
url: '/order/reconcile/detail/page',
params,
})
}
/**
* 明细详情
*/
export function get(id: string) {
return defHttp.get<Result<ReconcileDetail>>({
url: '/order/reconcile/detail/findById',
params: { id },
})
}
/**
* 通用支付对账记录
*/
export interface ReconcileDetail extends BaseEntity {
// 关联对账订单ID
recordOrderId?: string
// 交易类型
type?: string
// 订单id
paymentId?: string
// 订单id
refundId?: string
// 网关订单号
gatewayOrderNo?: string
// 交易金额
amount?: string
// 商品名称
title?: string
}

View File

@@ -9,23 +9,20 @@
@cancel="handleCancel"
>
<a-descriptions bordered title="" :column="{ md: 1, sm: 1, xs: 1 }">
<a-descriptions-item label="商品名称">
<a-descriptions-item label="订单名称">
{{ form.title }}
</a-descriptions-item>
<a-descriptions-item label="交易金额">
{{ form.amount }}
</a-descriptions-item>
<a-descriptions-item label="支付订单ID">
{{ form.paymentId }}
</a-descriptions-item>
<a-descriptions-item label="退款订单ID" v-if="form.refundId">
{{ form.refundId }}
<a-descriptions-item label="本地订单ID">
{{ form.orderId }}
</a-descriptions-item>
<a-descriptions-item label="网关订单号">
{{ form.gatewayOrderNo }}
</a-descriptions-item>
<a-descriptions-item label="交易类型">
<a-tag>{{ dictConvert('PayReconcileTrade', form.type) }}</a-tag>
<a-tag>{{ dictConvert('ReconcileTrade', form.type) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ form.createTime }}
@@ -44,7 +41,7 @@
import { FormInstance } from 'ant-design-vue/lib/form'
import { BasicModal } from '/@/components/Modal'
import { useDict } from '/@/hooks/bootx/useDict'
import { getDetail, ReconcileDetail } from './ReconcileOrder.api'
import { get, ReconcileDetail } from './ReconcileDetail.api'
const {
initFormEditType,
handleCancel,
@@ -70,7 +67,7 @@
function init(record) {
visible.value = true
confirmLoading.value = true
getDetail(record.id).then(({ data }) => {
get(record.id).then(({ data }) => {
form = data
confirmLoading.value = false
})

View File

@@ -2,23 +2,29 @@
<basic-drawer forceRender v-bind="$attrs" title="对账单明细列表" width="60%" :visible="visible" @close="visible = false">
<b-query :query-params="model.queryParam" :default-item-count="3" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table row-id="id" ref="xTable" :data="pagination.records" :loading="loading">
<vxe-table
row-id="id"
ref="xTable"
:data="pagination.records"
:loading="loading"
:sort-config="{ remote: true, trigger: 'cell' }"
@sort-change="sortChange"
>
<vxe-column type="seq" width="60" />
<vxe-column field="title" title="商品名称" />
<vxe-column field="title" title="订单名称" />
<vxe-column field="amount" title="交易金额" />
<vxe-column field="paymentId" title="支付订单ID" />
<vxe-column field="refundId" title="退款订单ID" />
<vxe-column field="orderId" title="本地订单ID" />
<vxe-column field="gatewayOrderNo" title="网关订单号" />
<vxe-column field="repairType" title="交易类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('PayReconcileTrade', row.type) }}</a-tag>
<a-tag>{{ dictConvert('ReconcileTrade', row.type) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="createTime" title="创建时间" />
<vxe-column field="orderTime" title="订单时间" sortable />
<vxe-column fixed="right" width="60" :showOverflow="false" title="操作">
<template #default="{ row }">
<span>
<a href="javascript:" @click="show(row)">查看</a>
<a-link @click="show(row)">查看</a-link>
</span>
</template>
</vxe-column>
@@ -38,19 +44,21 @@
<script setup lang="ts">
import { computed, nextTick, onMounted } from 'vue'
import { $ref } from 'vue/macros'
import { pageDetail, ReconcileDetail } from './ReconcileOrder.api'
import { page, ReconcileDetail } from './ReconcileDetail.api'
import useTablePage from '/@/hooks/bootx/useTablePage'
import ReconcileDetailInfo from './ReconcileDetailInfo.vue'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { useMessage } from '/@/hooks/web/useMessage'
import { LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
import { useDict } from '/@/hooks/bootx/useDict'
import ALink from '/@/components/Link/Link.vue'
// 使hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, pages, model, loading } = useTablePage(queryPage)
const { handleTableChange, pageQueryResHandel, resetQueryParams, pagination, sortChange, pages, sortParam, model, loading } =
useTablePage(queryPage)
const { notification, createMessage } = useMessage()
const { dictDropDown, dictConvert } = useDict()
@@ -60,10 +68,9 @@
const fields = computed(() => {
return [
{ field: 'title', type: STRING, name: '订单名称', placeholder: '请输入订单名称' },
{ field: 'paymentId', type: STRING, name: '本地支付ID', placeholder: '请输入本地支付ID' },
{ field: 'refundId', type: STRING, name: '本地退款ID', placeholder: '请输入本地退款ID' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入网关订单号' },
{ field: 'type', type: LIST, name: '交易类型', placeholder: '请选择交易类型', selectList: reconcileTradeList },
{ field: 'orderId', type: STRING, name: '本地订单', placeholder: '请输入本地订单ID' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入网关订单号' },
] as QueryField[]
})
let visible = $ref(false)
@@ -85,7 +92,7 @@
* 初始化基础数据
*/
async function initData() {
reconcileTradeList = await dictDropDown('PayReconcileTrade')
reconcileTradeList = await dictDropDown('ReconcileTrade')
}
/**
* 入口
@@ -94,6 +101,8 @@
function init(record) {
visible = true
reconcileDetail = record
model.queryParam = {}
xTable?.clearSort()
queryPage()
}
@@ -102,9 +111,10 @@
*/
function queryPage() {
loading.value = true
pageDetail({
page({
...model.queryParam,
...pages,
...sortParam,
recordOrderId: reconcileDetail?.id,
}).then(({ data }) => {
pageQueryResHandel(data)

View File

@@ -0,0 +1,57 @@
import { defHttp } from '/@/utils/http/axios'
import { PageResult, Result } from '/#/axios'
import { BaseEntity } from '/#/web'
/**
* 对账差异分页
*/
export function page(params) {
return defHttp.get<Result<PageResult<ReconcileRecord>>>({
url: '/order/reconcile/diff/page',
params,
})
}
/**
* 对账差异详情
*/
export function get(id: string) {
return defHttp.get<Result<ReconcileRecord>>({
url: '/order/reconcile/diff/findById',
params: { id },
})
}
/**
* 通用支付对账记录
*/
export interface ReconcileRecord extends BaseEntity {
// 关联对账订单ID
recordOrderId?: string
// 交易类型
type?: string
// 订单id
paymentId?: string
// 订单id
refundId?: string
// 网关订单号
gatewayOrderNo?: string
// 交易金额
amount?: string
// 商品名称
title?: string
// 差异内容
diffs?: ReconcileDiff[]
}
/**
* 差异内容
*/
export interface ReconcileDiff {
// 字段名
fieldName?: string
// 本地订单字段值
localValue?: string
// 网关订单字段值
gatewayValue?: string
}

View File

@@ -0,0 +1,95 @@
<template>
<basic-modal
title="对账订单详情"
v-bind="$attrs"
:loading="confirmLoading"
:width="750"
:visible="visible"
:mask-closable="showable"
@cancel="handleCancel"
>
<a-descriptions bordered title="" :column="{ md: 1, sm: 1, xs: 1 }">
<a-descriptions-item label="订单标题">
{{ form.title }}
</a-descriptions-item>
<a-descriptions-item label="交易金额">
{{ form.amount }}
</a-descriptions-item>
<a-descriptions-item label="对账单ID">
{{ form.recordId }}
</a-descriptions-item>
<a-descriptions-item v-if="form.detailId" label="对账单明细ID">
{{ form.detailId }}
</a-descriptions-item>
<a-descriptions-item label="本地订单ID">
{{ form.orderId }}
</a-descriptions-item>
<a-descriptions-item v-if="form.gatewayOrderNo" label="网关订单号">
{{ form.gatewayOrderNo }}
</a-descriptions-item>
<a-descriptions-item label="订单类型">
<a-tag>{{ dictConvert('ReconcileTrade', form.orderType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="差异类型">
<a-tag>{{ dictConvert('ReconcileDiffType', form.diffType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="订单时间">
{{ form.orderTime }}
</a-descriptions-item>
<template v-if="form.diffs">
<a-descriptions-item :label="`${item.fieldName}[差异]`" :key="item" v-for="item in form.diffs">
<a-tag>本地</a-tag> : {{ item.localValue }}<br /><a-tag>远程</a-tag> : {{ item.gatewayValue }}
</a-descriptions-item>
</template>
</a-descriptions>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
</a-space>
</template>
</basic-modal>
</template>
<script setup lang="ts">
import { $ref } from 'vue/macros'
import useFormEdit from '/@/hooks/bootx/useFormEdit'
import { FormInstance } from 'ant-design-vue/lib/form'
import { BasicModal } from '/@/components/Modal'
import { useDict } from '/@/hooks/bootx/useDict'
import { get, ReconcileDiff, ReconcileRecord } from './ReconcileDiff.api'
const {
initFormEditType,
handleCancel,
search,
labelCol,
wrapperCol,
modalWidth,
title,
confirmLoading,
visible,
editable,
showable,
formEditType,
} = useFormEdit()
const { dictConvert } = useDict()
// 表单
const formRef = $ref<FormInstance>()
let form = $ref<ReconcileOrder>({})
// 事件
const emits = defineEmits(['ok'])
// 入口
function init(record) {
visible.value = true
confirmLoading.value = true
get(record.id).then(({ data }) => {
form = data
confirmLoading.value = false
})
}
defineExpose({
init,
})
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,119 @@
<template>
<div>
<div class="m-3 p-3 pt-5 bg-white">
<b-query :query-params="model.queryParam" :default-item-count="3" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
</div>
<div class="m-3 p-3 bg-white">
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
row-id="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="title" title="订单标题" />
<vxe-column field="orderType" title="订单类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ReconcileTrade', row.orderType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="diffType" title="差异类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ReconcileDiffType', row.diffType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="orderId" title="本地订单" />
<vxe-column field="gatewayOrderNo" title="网关订单" />
<vxe-column field="amount" title="交易金额" />
<vxe-column field="orderTime" title="订单时间" />
<vxe-column fixed="right" width="80" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
</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"
/>
<reconcile-diff-info ref="reconcileDiffInfo" />
</div>
</div>
</template>
<script setup lang="ts">
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import useTablePage from '/@/hooks/bootx/useTablePage'
import { useMessage } from '/@/hooks/web/useMessage'
import { useDict } from '/@/hooks/bootx/useDict'
import { LabeledValue } from 'ant-design-vue/lib/select'
import { computed, onMounted } from 'vue'
import { DATE, LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { page } from './ReconcileDiff.api'
import ReconcileDiffInfo from './ReconcileDiffInfo.vue'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, sortChange, sortParam, pagination, pages, model, loading } =
useTablePage(queryPage)
const { notification, createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
let payChannelList = $ref<LabeledValue[]>([])
// 查询条件
const fields = computed(() => {
return [
{ field: 'title', type: DATE, name: '订单标题', placeholder: '请输入订单标题' },
] as QueryField[]
})
const xTable = $ref<VxeTableInstance>()
const xToolbar = $ref<VxeToolbarInstance>()
const reconcileDiffInfo = $ref<any>()
onMounted(() => {
initData()
vxeBind()
queryPage()
})
function vxeBind() {
xTable?.connect(xToolbar as VxeToolbarInstance)
}
/**
* 初始化数据
*/
async function initData() {
payChannelList = await dictDropDown('AsyncPayChannel')
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
...sortParam,
}).then(({ data }) => {
pageQueryResHandel(data)
})
return Promise.resolve()
}
/**
* 查看详情
*/
function show(record) {
reconcileDiffInfo.init(record)
}
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,139 @@
<template>
<basic-drawer forceRender v-bind="$attrs" title="差异明细列表" width="60%" :visible="visible" @close="visible = false">
<b-query :query-params="model.queryParam" :default-item-count="3" :fields="fields" @query="queryPage" @reset="resetQueryParams" />
<vxe-toolbar ref="xToolbar" custom :refresh="{ queryMethod: queryPage }" />
<vxe-table
row-id="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="title" title="订单标题" />
<vxe-column field="orderType" title="订单类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ReconcileTrade', row.orderType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="diffType" title="差异类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ReconcileDiffType', row.diffType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="orderId" title="本地订单" />
<vxe-column field="gatewayOrderNo" title="网关订单" />
<vxe-column field="amount" title="交易金额" sortable />
<vxe-column field="orderTime" title="订单时间" sortable />
<vxe-column fixed="right" width="80" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
</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"
/>
<reconcile-diff-info ref="reconcileDiffInfo" />
</basic-drawer>
</template>
<script setup lang="ts">
import { computed, nextTick, onMounted } from 'vue'
import { $ref } from 'vue/macros'
import { page } from './ReconcileDiff.api'
import useTablePage from '/@/hooks/bootx/useTablePage'
import ReconcileDiffInfo from './ReconcileDiffInfo.vue'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { useMessage } from '/@/hooks/web/useMessage'
import { LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import BasicDrawer from '/@/components/Drawer/src/BasicDrawer.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { LabeledValue } from 'ant-design-vue/lib/select'
import { useDict } from '/@/hooks/bootx/useDict'
import { ReconcileDetail } from '/@/views/payment/order/reconcile/detail/ReconcileDetail.api'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, sortChange, pagination, pages, sortParam, model, loading } =
useTablePage(queryPage)
const { notification, createMessage } = useMessage()
const { dictDropDown, dictConvert } = useDict()
let orderTypeList = $ref<LabeledValue[]>([])
let diffTypeList = $ref<LabeledValue[]>([])
// 查询条件
const fields = computed(() => {
return [
{ field: 'title', type: STRING, name: '订单名称', placeholder: '请输入订单名称' },
{ field: 'orderType', type: LIST, name: '订单类型', placeholder: '请选择订单类型', selectList: orderTypeList },
{ field: 'diffType', type: LIST, name: '差异类型', placeholder: '请选择差异类型', selectList: diffTypeList },
{ field: 'orderId', type: STRING, name: '本地定单', placeholder: '请输入本地定单ID' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入网关订单号' },
] as QueryField[]
})
let visible = $ref(false)
let reconcileDetail = $ref<ReconcileDetail>()
const xTable = $ref<VxeTableInstance>()
const xToolbar = $ref<VxeToolbarInstance>()
const reconcileDiffInfo = $ref<any>()
nextTick(() => {
xTable?.connect(xToolbar as VxeToolbarInstance)
})
onMounted(() => {
initData()
})
/**
* 初始化基础数据
*/
async function initData() {
orderTypeList = await dictDropDown('ReconcileTrade')
diffTypeList = await dictDropDown('ReconcileDiffType')
}
/**
* 入口
*/
function init(record: ReconcileDetail) {
visible = true
reconcileDetail = record
model.queryParam = {}
xTable?.clearSort()
queryPage()
}
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
...model.queryParam,
...pages,
...sortParam,
recordId: reconcileDetail?.id,
}).then(({ data }) => {
pageQueryResHandel(data)
})
}
/**
* 查看
*/
function show(record) {
reconcileDiffInfo.init(record)
}
defineExpose({
init,
})
</script>
<style scoped lang="less"></style>

View File

@@ -32,26 +32,6 @@ export function get(id: string) {
})
}
/**
*
*/
export function pageDetail(params) {
return defHttp.get<Result<PageResult<ReconcileDetail>>>({
url: '/order/reconcile/pageDetail',
params,
})
}
/**
*
*/
export function getDetail(id: string) {
return defHttp.get<Result<ReconcileDetail>>({
url: '/order/reconcile/findDetailById',
params: { id },
})
}
/**
*
*/
@@ -62,6 +42,16 @@ export function downAndSave(id: any) {
})
}
/**
*
*/
export function compare(id: any) {
return defHttp.post<any>({
url: '/order/reconcile/compare',
params: { id },
})
}
/**
*
*/
@@ -79,23 +69,3 @@ export interface ReconcileOrder extends BaseEntity {
// 错误信息
errorMsg?: string
}
/**
*
*/
export interface ReconcileDetail extends BaseEntity {
// 关联对账订单ID
recordOrderId?: string
// 交易类型
type?: string
// 订单id
paymentId?: string
// 订单id
refundId?: string
// 网关订单号
gatewayOrderNo?: string
// 交易金额
amount?: string
// 商品名称
title?: string
}

View File

@@ -29,6 +29,9 @@
<a-descriptions-item label="错误信息">
{{ form.errorMsg }}
</a-descriptions-item>
<a-descriptions-item label="创建时间">
{{ form.createTime }}
</a-descriptions-item>
</a-descriptions>
<template #footer>
<a-space>

View File

@@ -36,15 +36,28 @@
<vxe-column field="compare" title="对账单比对">
<template #default="{ row }">
<a-tag v-if="row.compare" color="green">已比对</a-tag>
<a-link v-else :disabled="!row.down" color="red" @click="compare(row)">比对</a-link>
<a-link v-else :disabled="!row.down" color="red" @click="compareOrder(row)">比对</a-link>
</template>
</vxe-column>
<vxe-column field="errorMsg" title="错误信息" />
<vxe-column fixed="right" width="140" :showOverflow="false" title="操作">
<vxe-column field="createTime" title="创建时间" />
<vxe-column fixed="right" width="120" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
<a-divider type="vertical" />
<a-link @click="showDetailPage(row)">对账明细</a-link>
<a-dropdown>
<a> 更多 <icon icon="ant-design:down-outlined" :size="12" /></a>
<template #overlay>
<a-menu>
<a-menu-item>
<a-link @click="showDetailPage(row)">对账明细</a-link>
</a-menu-item>
<a-menu-item>
<a-link @click="showDiffPage(row)">差异明细</a-link>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</template>
</vxe-column>
</vxe-table>
@@ -60,6 +73,7 @@
<reconcile-order-create ref="reconcileOrderCreate" @ok="queryPage" />
<reconcile-detail-list ref="reconcileDetailList" />
<reconcile-order-info ref="reconcileOrderInfo" />
<reconcile-diff-list-model ref="reconcileDiffListModel" />
</div>
</template>
<script setup lang="ts">
@@ -71,13 +85,16 @@
import { computed, onMounted } from 'vue'
import { DATE, LIST, QueryField, STRING } from '/@/components/Bootx/Query/Query'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import { downAndSave, page } from './ReconcileOrder.api'
import { compare, downAndSave, page } from './ReconcileOrder.api'
import ReconcileOrderInfo from './ReconcileOrderInfo.vue'
import ReconcileDetailList from './ReconcileDetailList.vue'
import ReconcileOrderCreate from '/@/views/payment/order/reconcile/ReconcileOrderCreate.vue'
import ReconcileDetailList from '../detail/ReconcileDetailList.vue'
import ReconcileOrderCreate from './ReconcileOrderCreate.vue'
import ALink from '/@/components/Link/Link.vue'
import ReconcileDiffListModel from '/@/views/payment/order/reconcile/diff/ReconcileDiffListModel.vue'
// 使hooks
const { handleTableChange, pageQueryResHandel, resetQueryParams, sortChange, sortParam, pagination, pages, model, loading } = useTablePage(queryPage)
const { handleTableChange, pageQueryResHandel, resetQueryParams, sortChange, sortParam, pagination, pages, model, loading } =
useTablePage(queryPage)
const { notification, createMessage, createConfirm } = useMessage()
const { dictConvert, dictDropDown } = useDict()
@@ -103,6 +120,7 @@
const reconcileOrderCreate = $ref<any>()
const reconcileOrderInfo = $ref<any>()
const reconcileDetailList = $ref<any>()
const reconcileDiffListModel = $ref<any>()
onMounted(() => {
initData()
@@ -151,10 +169,13 @@
content: '确定要下载对账单吗?',
onOk: () => {
createMessage.info('对账单下载保存中.....')
downAndSave(record.id).then(() => {
createMessage.info('对账单下载完成')
queryPage()
})
downAndSave(record.id)
.then(() => {
createMessage.info('对账单下载完成')
})
.finally(() => {
queryPage()
})
},
})
}
@@ -162,8 +183,22 @@
/**
* 对账明显比对
*/
function compare(record) {
createMessage.warn('下个版本支持...')
function compareOrder(record) {
createConfirm({
iconType: 'info',
title: '提示',
content: '确定要进行比对吗?',
onOk: () => {
createMessage.info('对账单比对中.....')
compare(record.id)
.then(() => {
createMessage.info('对账单比对完成')
})
.finally(() => {
queryPage()
})
},
})
}
/**
@@ -179,6 +214,13 @@
function showDetailPage(record) {
reconcileDetailList.init(record)
}
/**
* 查看差异单
*/
function showDiffPage(record) {
reconcileDiffListModel.init(record)
}
</script>
<style scoped lang="less"></style>

View File

@@ -62,6 +62,16 @@ export function syncById(id) {
})
}
/**
* 退款重试
*/
export function resetRefund(id){
return defHttp.post<Result<void>>({
url: '/order/refund/resetRefund',
params: { id },
})
}
/**
* 退款记录
*/

View File

@@ -46,14 +46,17 @@
</template>
</vxe-column>
<vxe-column field="reason" title="原因" />
<vxe-column fixed="right" width="180" :showOverflow="false" title="操作">
<vxe-column fixed="right" width="220" :showOverflow="false" title="操作">
<template #default="{ row }">
<a-link @click="show(row)">查看</a-link>
<a-divider type="vertical" />
<a-link @click="showChannel(row)">通道订单</a-link>
<a-divider type="vertical" />
<!-- 只有退款中的异步订单才可以同步 -->
<a-link :disabled="!(row.asyncPay && row.status === 'progress')" @click="sync(row)">同步</a-link>
<!-- 只有异步订单才可以同步 -->
<a-link :disabled="!row.asyncPay" @click="sync(row)">同步</a-link>
<a-divider type="vertical" />
<!-- 只有退款失败的异步订单才可以重新退款 -->
<a-link :disabled="!(row.asyncPay && row.status === 'fail')" @click="reset(row)">重试</a-link>
</template>
</vxe-column>
</vxe-table>
@@ -75,7 +78,7 @@
<script lang="ts" setup>
import { computed, onMounted } from 'vue'
import { $ref } from 'vue/macros'
import { page, syncById } from './RefundOrder.api'
import { page, resetRefund, syncById } from './RefundOrder.api'
import useTablePage from '/@/hooks/bootx/useTablePage'
import RefundOrderInfo from './RefundOrderInfo.vue'
import { VxeTable, VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
@@ -99,8 +102,9 @@
// 查询条件
const fields = computed(() => {
return [
{ field: 'id', type: STRING, name: '退款', placeholder: '请输入完整退款' },
{ field: 'paymentId', type: STRING, name: '原支付单号', placeholder: '请输入完整支付ID' },
{ field: 'id', type: STRING, name: '退款ID', placeholder: '请输入完整退款ID' },
{ field: 'refundNo', type: STRING, name: '退款号', placeholder: '请输入完整退款号' },
{ field: 'paymentId', type: STRING, name: '原支付ID', placeholder: '请输入完整支付ID' },
{ field: 'businessNo', type: STRING, name: '原业务号', placeholder: '请输入业务号' },
{ field: 'gatewayOrderNo', type: STRING, name: '网关订单号', placeholder: '请输入完整网关订单号' },
{ field: 'title', type: STRING, name: '原支付标题', placeholder: '请输入原支付标题' },
@@ -171,6 +175,24 @@
})
}
/**
* 退款重试
*/
function reset(record) {
createConfirm({
iconType: 'warning',
title: '警告',
content: '是否同步退款信息',
onOk: () => {
loading.value = true
resetRefund(record.id).then(() => {
createMessage.success('提交成功')
queryPage()
})
},
})
}
/**
* 查看
*/

View File

@@ -14,10 +14,10 @@
{{ info.id }}
</a-descriptions-item>
<a-descriptions-item label="请求次数">
{{ info.reqCount }}
{{ info.reqCount || '空' }}
</a-descriptions-item>
<a-descriptions-item label="发送类型">
<a-tag>{{ dictConvert('ClientNoticeSendType', info.type) }}</a-tag>
<a-tag>{{ dictConvert('ClientNoticeSendType', info.sendType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="是否发送成功">
<a-tag v-if="info.success" color="green"></a-tag>

View File

@@ -10,16 +10,20 @@
@sort-change="sortChange"
>
<vxe-column type="seq" width="60" />
<vxe-column field="reqCount" title="请求次数" />
<vxe-column field="reqCount" title="请求次数">
<template #default="{ row }">
<a-tag color="green">{{ row.reqCount || '空' }}</a-tag>
</template>
</vxe-column>
<vxe-column field="channel" title="是否成功">
<template #default="{ row }">
<a-tag v-if="row.success" color="green"></a-tag>
<a-tag v-else color="red"></a-tag>
</template>
</vxe-column>
<vxe-column field="type" title="发送类型">
<vxe-column field="sendType" title="发送类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ClientNoticeSendType', row.type) }}</a-tag>
<a-tag>{{ dictConvert('ClientNoticeSendType', row.sendType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="errorMsg" title="错误信息" max-width="200" />

View File

@@ -61,8 +61,8 @@ export function getRecord(id) {
export interface ClientNoticeTask extends BaseEntity {
// 本地订单ID
orderId?: number
// 回调类型
type?: string
// 消息类型
noticeType?: string
// 消息内容
content?: string
// 是否发送成功
@@ -83,6 +83,8 @@ export interface ClientNoticeRecord extends BaseEntity {
taskId?: string
// 请求次数
reqCount?: number
// 发送类型
sendType?: boolean
// 发送是否成功
success?: boolean
// 错误信息

View File

@@ -17,7 +17,11 @@
<a-tag>{{ task.orderId }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="回调类型">
<a-tag>{{ dictConvert('ClientNoticeType', task.type) }}</a-tag>
<a-tag>{{ dictConvert('ClientNoticeType', task.noticeType) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="订单状态">
<a-tag v-if="task.noticeType === 'pay'">{{ dictConvert('PayStatus', task.orderStatus) || '未知' }}</a-tag>
<a-tag v-else>{{ dictConvert('RefundStatus', task.orderStatus) || '未知' }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="消息内容">
<json-preview :data="JSON.parse(task.content || '{}')" />

View File

@@ -21,9 +21,15 @@
</a-link>
</template>
</vxe-column>
<vxe-column field="type" title="消息类型">
<vxe-column field="noticeType" title="消息类型">
<template #default="{ row }">
<a-tag>{{ dictConvert('ClientNoticeType', row.type) }}</a-tag>
<a-tag>{{ dictConvert('ClientNoticeType', row.noticeType) }}</a-tag>
</template>
</vxe-column>
<vxe-column field="success" title="订单状态">
<template #default="{ row }">
<a-tag v-if="row.noticeType === 'pay'">{{ dictConvert('PayStatus', row.orderStatus) || '未知' }}</a-tag>
<a-tag v-else>{{ dictConvert('RefundStatus', row.orderStatus) || '未知' }}</a-tag>
</template>
</vxe-column>
<vxe-column field="success" title="发送成功">
@@ -151,9 +157,8 @@
/**
* 查看订单信息
*/
function showOrder(record) {
console.log(record)
if (record.type === 'pay') {
function showOrder(record: ClientNoticeTask) {
if (record.noticeType === 'pay') {
payOrderInfo.init(record.orderId)
} else {
refundOrderInfo.init(record.orderId)

View File

@@ -3,9 +3,8 @@
<template #headerContent>
<div class="flex justify-between items-center">
<span class="flex-1">
<a :href="GITHUB_URL" target="_blank">Bootx-Platform </a>
是一个基于Spring BootVueAnt-Design-Vue
TypeScript的后台管理脚手架包含支付收单(支付宝微信聚合组合支付)工作流(Flowable)三方对接(微信钉钉企微短信)等功能
<a :href="GITHUB_URL" target="_blank">dax-pay </a>
是一款免费开源的支付网关独立部署通过HTTP方式进行调用不与其他系统产生耦合关联可以快速集成到各种系统中提供可视化界面进行管理便于实现统一的支付信息管理
</span>
</div>
</template>

View File

@@ -0,0 +1,57 @@
感谢您选择DaxPay支付系统以下简称DaxPayDaxPay是一款支付系统基于 Java 的技术开发,全部源码开放。
为了使您正确并合法地使用本软件,请您在使用前务必阅读清楚下面的协议条款:
一、本授权协议适用且仅适用于 DaxPay 2.X.X 版本,如果不同意该协议,请不要进行使用
二、 产品介绍和产品模式
1. DaxPay支付系统是由济南易杯光年软件技术有限公司研发的支付产品版权归济南易杯光年软件技术有限公司所有
2. DaxPay旨在解决需要支付交易流的开发者、个体户、公司等提供便捷的支付产品
3. DaxPay只是一个应用软件用户部署后只负责信息流的传递不负责资金流支付资金全部由用户选择的支付平台直接结算
4. DaxPay不会私自上传任何数据到外部但无法保证所系统中三方SDK和依赖是否会对外传输数据
三、协议许可的权利
1. 您可以在完全遵守本最终用户授权使用协议的基础上,将本软件应用于商业用途,而不必支付软件版权授权费用
2. 您可以在协议规定的约束和限制范围内修改 DaxPay 源代码或界面风格以适应您的要求
3. 您拥有使用本软件构建的网站全部内容所有权,并独立承担与这些内容的相关法律义务
四、有限担保和免责声明
1. 本软件及所附带的文件是作为不提供任何明确的或隐含的赔偿或担保的形式提供的。
2. 用户出于自愿而使用本软件,您必须了解使用本软件的风险,我们不承诺对用户提供任何形式的技术支持、使用担保,也不承担任何因使用本软件而产生问题的相关责任。
3. 电子文本形式的授权协议如同双方书面签署的协议一样具有完全的和等同的法律效力。您一旦开始确认本协议并使用DaxPay即被视为完全理解并接受本协议的各项条款在享有上述条款授予的权力的同时受到相关的约束和限制。协议许可范围以外的行为将直接违反本授权协议并构成侵权我们有权随时终止授权责令停止损害并保留追究相关责任的权力。
4. DaxPay系统由用户自行部署使用不提供在线服务、服务托管等服务用户对外提供服务时请保证已经根据国家相关法律法规获取到相应的许可
5. 如果本软件带有其它软件的整合API示范例子包这些文件版权不属于本软件官方并且这些文件是没经过授权发布的请参考相关软件的使用许可合法的使用。
6. 客户不得利用DaxPay产品从事非法行为客户应当合法合规的使用产品DaxPay不承担客户因非法行为造成的任何法律责任。
7. 如发现客户在使用产品时有任何的非法行为,有权解除授权停止技术支持,并配合有关机关进行调查或向政府部门举报。
8. 本软件由济南易杯光年软件技术有限公司进行分发,没有授权分销商,也没有任何分公司、代理商、办事处、经销商等销售和分发本产品
五、禁止接入的内容包括但不限于以下内容
1. 诈骗、BC严禁接入、赛车、V盘、X资金盘、贷款、P2P、汇兑、ICO、二清支付严禁接入
2. 1元购类、高额返利类 、多级分销类、盲盒类、单纯形式抽奖类、发卡平台严禁接入
3. 恋爱话术类、数字藏品、色情、政治相关、影视小说、电子书、游戏平台严禁接入
4. 付费入群、帐号销售、刷单、卡盟平台、刷粉、VPN、售卖假货严禁接入
5. 未取得相关资质的游戏、私服等禁止接入
6. 微信/支付宝/银联官方要求禁止接入的、其他支付通道禁止接入的、以及所有国家法规规定禁止的一切情景
协议日期2023年01月01日
济南易杯光年软件技术有限公司