feat 结算台和各种支付记录

This commit is contained in:
xxm1995
2023-06-21 16:51:27 +08:00
parent 89ba54aa24
commit d147741533
17 changed files with 183 additions and 92 deletions

View File

@@ -0,0 +1,25 @@
/**
* 支付状态
*/
export enum PayStatus {
/** 未知状态 */
TRADE_UNKNOWN = 'trade_unknown',
/** 支付中 */
TRADE_PROGRESS = 'trade_progress',
/** 成功 */
TRADE_SUCCESS = 'trade_success',
/** 失败 */
TRADE_FAIL = 'trade_fail',
/** 支付取消(超时/手动取消/订单已经关闭撤销支付单) */
TRADE_CANCEL = 'trade_cancel',
/** 退款中(部分退款) */
TRADE_REFUNDING = 'trade_refunding',
/** 已退款 */
TRADE_REFUNDED = 'trade_refunded',
}

View File

@@ -1,5 +1,6 @@
import { defHttp } from '/@/utils/http/axios'
import { Result } from '/#/axios'
import { PayStatus } from "/@/enums/payment/PayStatus";
/**
* 单独支付
@@ -36,8 +37,8 @@ export function combinationPay (obj) {
/**
* 根据业务ID获取支付状态
*/
export function findStatusByBusinessId(businessId) {
return defHttp.get({
export function findStatusByBusinessId(businessId){
return defHttp.get<Result<PayStatus>>({
url: '/payment/findStatusByBusinessId',
method: 'GET',
params: { businessId },

View File

@@ -113,15 +113,16 @@
import QrCode from '/@/components/Qrcode/src/Qrcode.vue'
import { useIntervalFn } from '@vueuse/core'
import { findByParamKey } from '/@/api/common/Parameter'
import { payChannelEnum } from '/@/enums/payChannelEnum'
import { payWayEnum } from '/@/enums/payWayEnum'
import { payChannelEnum } from '/@/enums/payment/payChannelEnum'
import { payWayEnum } from '/@/enums/payment/payWayEnum'
import { PayStatus } from '/@/enums/payment/PayStatus'
const { createMessage } = useMessage()
const cashierQrCode = $ref<any>()
const cashierBarCode = $ref<any>()
// 商户和通道
// 商户和应用编码
let mchCode = $ref<string>()
let mchAppCode = $ref<string>()
@@ -203,11 +204,11 @@
() => {
findStatusByBusinessId(businessId).then((res) => {
// 成功
if (res.data === 1) {
if (res.data === PayStatus.TRADE_SUCCESS) {
createMessage.success('支付成功')
handleCancel()
}
if ([2, 3].includes(res.data)) {
if ([PayStatus.TRADE_FAIL, PayStatus.TRADE_CANCEL].includes(res.data)) {
createMessage.error('支付失败')
handleCancel()
}
@@ -304,7 +305,7 @@
// 获取聚合支付的标识key
const { data: qrKey } = await createAggregatePay(param)
// 发起支付
const qrUrl = `${cashierAggregateUrl}/cashier/aggregatePay/${mchAppCode}?key=${qrKey}`
const qrUrl = `${cashierAggregateUrl}/cashier/aggregatePay/${mchCode}/${mchAppCode}?key=${qrKey}`
cashierQrCode.init(qrUrl, '请使用支付宝或微信"扫一扫"扫码支付')
resume()
}

View File

@@ -1,8 +1,8 @@
<template>
<a-modal :visible="visible" title="扫码支付" @cancel="handleCancel" :footer="null" :width="250">
<div style="display: flex; flex-direction: column; align-items: center">
<div style="display: flex; flex-direction: column; align-items: center; padding-top: 15px">
<qr-code :options="{ margin: 0 }" :width="180" :value="qrUrl" />
<div class="mt-15px" style="display: flex; flex-direction: row; align-items: center; justify-content: center">
<div class="mt-15px mb-15px" style="display: flex; flex-direction: row; align-items: center; justify-content: center">
{{ bottomTitle }}
</div>
</div>

View File

@@ -21,12 +21,12 @@
<a-form-item label="异步支付金额" name="asyncAmount">
<a-input-number placeholder="请输入金额" :precision="2" v-model:value="form.asyncAmount" />
</a-form-item>
<a-form-item label="钱包支付" name="walletAmount">
<a-input-number placeholder="请输入金额" :precision="2" v-model:value="form.walletAmount" />
<template #help>
<span>钱包余额{{ wallet.balance }}</span>
</template>
</a-form-item>
<!-- <a-form-item label="钱包支付" name="walletAmount">-->
<!-- <a-input-number placeholder="请输入金额" :precision="2" v-model:value="form.walletAmount" />-->
<!-- <template #help>-->
<!-- <span>钱包余额{{ wallet.balance }}</span>-->
<!-- </template>-->
<!-- </a-form-item>-->
<a-form-item label="现金支付" name="cashAmount">
<a-input-number placeholder="请输入金额" :precision="2" v-model:value="form.cashAmount" />
</a-form-item>
@@ -50,7 +50,11 @@
import { useIntervalFn } from '@vueuse/core'
import { onMounted } from 'vue'
import CashierQrCode from './CashierQrCode.vue'
import { Wallet } from "/@/views/modules/payment/wallet/list/Wallet.api";
import { Wallet } from '/@/views/modules/payment/wallet/list/Wallet.api'
import { payChannelEnum } from '/@/enums/payment/payChannelEnum'
import { payWayEnum } from '/@/enums/payment/payWayEnum'
import { findByParamKey } from '/@/api/common/Parameter'
import { PayStatus } from '/@/enums/payment/PayStatus'
const { createMessage } = useMessage()
@@ -64,27 +68,29 @@
sm: { span: 13 },
}
const payChannel = [
{ code: 1, label: '支付宝' },
{ code: 2, label: '微信' },
{ code: payChannelEnum.ALI, label: '支付宝' },
{ code: payChannelEnum.WECHAT, label: '微信' },
]
let loading = $ref(false)
let visible = $ref(false)
let wallet = $ref<Wallet>({ balance: 0 })
// let wallet = $ref<Wallet>({ balance: 0 })
let voucher = $ref<Voucher>({})
let form = $ref({
payChannel: 1,
payWay: 4,
payChannel: payChannelEnum.ALI,
payWay: payWayEnum.QRCODE,
businessId: '',
title: '测试支付订单',
asyncAmount: null,
walletAmount: null,
// walletAmount: null,
cashAmount: null,
mchCode: '',
mchAppCode: '',
})
const rules = {
payChannel: [{ required: true, message: '不可为空' }],
businessId: [{ required: true, message: '不可为空' }],
title: [{ required: true, message: '不可为空' }],
payWay: [{ required: true, message: '不可为空' }],
payChannel: [{ required: true, message: '支付通道不可为空' }],
businessId: [{ required: true, message: '业务ID不可为空' }],
title: [{ required: true, message: '标题不可为空' }],
payWay: [{ required: true, message: '支付方式不可为空' }],
}
// 检查支付状态
@@ -92,11 +98,11 @@
() => {
findStatusByBusinessId(form.businessId).then((res) => {
// 成功
if (res.data === 1) {
if (res.data === PayStatus.TRADE_SUCCESS) {
createMessage.success('支付成功')
handleCancel()
}
if ([2, 3].includes(res.data)) {
if ([PayStatus.TRADE_FAIL, PayStatus.TRADE_CANCEL].includes(res.data)) {
createMessage.error('支付失败')
handleCancel()
}
@@ -110,11 +116,18 @@
init()
})
function init() {
findWalletByUser().then(({ data }) => {
wallet = data
})
/**
* 初始化数据
*/
async function init() {
// findWalletByUser().then(({ data }) => {
// wallet = data
// })
genOrderNo()
// 获取商户和应用编码
form.mchCode = (await findByParamKey('CashierMchCode')).data
form.mchAppCode = (await findByParamKey('CashierMchAppCode')).data
}
// 生成订单号
function genOrderNo() {
@@ -125,16 +138,18 @@
formRef?.validate().then(async () => {
loading = true
// 组装支付参数
const payModeList = [
const payWayList = [
{ payChannel: form.payChannel, payWay: form.payWay, amount: form.asyncAmount },
{ payChannel: 4, amount: form.cashAmount },
{ payChannel: 5, amount: form.walletAmount },
{ payChannel: payChannelEnum.CASH, payWay: payWayEnum.NORMAL, amount: form.cashAmount },
// { payChannel: 5, amount: form.walletAmount },
]
// 异步
const { data } = await combinationPay({
title: form.title,
businessId: form.businessId,
payModeList: payModeList,
payWayList: payWayList,
mchCode: form.mchCode,
mchAppCode: form.mchAppCode,
}).finally(() => (loading = false))
// 同步还是异步支付
if (data.asyncPayMode) {

View File

@@ -20,11 +20,8 @@
</a-form-item>
<a-form-item label="金额" name="amount">
<a-input-number :precision="2" :min="0.01" v-model:value="form.amount" />
<template v-if="form.payChannel === 5" #help>
<span>钱包余额{{ wallet.balance }}</span>
</template>
</a-form-item>
<a-form-item label="储值卡" name="voucherNo" v-if="form.payChannel === 6">
<a-form-item label="储值卡" name="voucherNo" v-if="form.payChannel === payChannelEnum.VOUCHER">
<a-input v-model:value="form.voucherNo" @blur="getVoucher" />
<template #help>
<span>储值卡面值{{ voucher.faceValue }} 余额{{ voucher.balance }}</span>
@@ -43,13 +40,17 @@
<script lang="ts" setup>
import { $ref } from 'vue/macros'
import { computed, onMounted } from 'vue'
import { onMounted } from 'vue'
import { findStatusByBusinessId, findWalletByUser, singlePay } from '/@/views/demo/payment/cashier/Cashier.api'
import { FormInstance } from 'ant-design-vue/lib/form'
import { useIntervalFn } from '@vueuse/core'
import { useMessage } from '/@/hooks/web/useMessage'
import { findByCardNo, Voucher } from '/@/views/modules/payment/voucher/Voucher.api'
import CashierQrCode from './CashierQrCode.vue'
import { payChannelEnum } from '/@/enums/payment/payChannelEnum'
import { findByParamKey } from '/@/api/common/Parameter'
import { payWayEnum } from '/@/enums/payment/payWayEnum'
import { PayStatus } from "/@/enums/payment/PayStatus";
const { createMessage } = useMessage()
@@ -62,25 +63,28 @@
const wrapperCol = {
sm: { span: 13 },
}
const payChannel = [
{ code: 1, name: '支付宝' },
{ code: 2, name: '微信' },
{ code: payChannelEnum.ALI, name: '支付宝' },
{ code: payChannelEnum.WECHAT, name: '微信' },
// { code: 3, name: '云闪付' },
{ code: 5, name: '钱包' },
{ code: 6, name: '储值卡' },
// { code: 5, name: '钱包' },
{ code: payChannelEnum.VOUCHER, name: '储值卡' },
]
let loading = $ref(false)
let visible = $ref(false)
let wallet = $ref({ balance: 0 })
// let wallet = $ref({ balance: 0 })
let voucher = $ref<Voucher>({})
let form = $ref({
payChannel: 1,
payWay: 4,
payChannel: payChannelEnum.ALI,
payWay: payWayEnum.QRCODE,
businessId: '',
voucherNo: '',
title: '测试支付订单',
// 二维码支付方式
amount: 0.01,
mchCode: '',
mchAppCode: '',
})
const rules = {
payChannel: [{ required: true, message: '不可为空' }],
@@ -96,11 +100,11 @@
() => {
findStatusByBusinessId(form.businessId).then((res) => {
// 成功
if (res.data === 1) {
if (res.data === PayStatus.TRADE_SUCCESS) {
createMessage.success('支付成功')
handleCancel()
}
if ([2, 3].includes(res.data)) {
if ([PayStatus.TRADE_FAIL, PayStatus.TRADE_CANCEL].includes(res.data)) {
createMessage.error('支付失败')
handleCancel()
}
@@ -111,16 +115,21 @@
)
onMounted(() => {
init()
initData()
})
// 初始化
function init() {
/**
* 初始化数据
*/
async function initData() {
// 获取钱包
findWalletByUser().then((res) => {
wallet = res.data
})
// findWalletByUser().then((res) => {
// wallet = res.data
// })
genOrderNo()
// 获取商户和应用编码
form.mchCode = (await findByParamKey('CashierMchCode')).data
form.mchAppCode = (await findByParamKey('CashierMchAppCode')).data
}
// 生成订单号
@@ -136,7 +145,8 @@
// 是否异步支付
if (data.asyncPayMode) {
if (data.payChannel === 1) {
console.log(data.asyncPayChannel)
if (data.asyncPayChannel === payChannelEnum.ALI) {
cashierQrCode.init(data.asyncPayInfo.payBody, '请使用支付宝"扫一扫"扫码支付')
} else {
cashierQrCode.init(data.asyncPayInfo.payBody, '请使用微信"扫一扫"扫码支付')
@@ -165,5 +175,4 @@
}
</script>
<style scoped>
</style>
<style scoped></style>

View File

@@ -159,8 +159,9 @@
// 校验
const rules = computed(() => {
return {
mchId: [{ required: true, message: '请输入商户号' }],
appId: [{ required: true, message: '请输入应用编号' }],
wxMchId: [{ required: true, message: '请输入商户号' }],
wxAppId: [{ required: true, message: '请输入应用编号' }],
appSecret: [{ required: true, message: '请输入AppSecret' }],
notifyUrl: [{ required: true, message: '请输入异步通知页面地址' }],
returnUrl: [{ required: true, message: '请输入同步通知页面地址' }],
domain: [{ required: true, message: '请输入请求应用域名' }],
@@ -208,7 +209,10 @@
if (formEditType.value === FormEditType.Add) {
await add(form)
} else if (formEditType.value === FormEditType.Edit) {
await update({ ...form, ...diffForm(rawForm, form, 'mchId', 'appId', 'appSecret', 'apiKeyV2', 'apiKeyV3', 'keyPem', 'certPem') })
await update({
...form,
...diffForm(rawForm, form, 'wxMchId', 'wxAppId', 'appSecret', 'apiKeyV2', 'apiKeyV3', 'keyPem', 'certPem'),
})
}
confirmLoading.value = false
handleCancel()

View File

@@ -20,7 +20,7 @@
<json-preview :data="JSON.parse(form.notifyInfo || '{}')" />
</a-descriptions-item>
<a-descriptions-item label="状态">
{{ form.status === 1 ? '成功' : '失败' }}
{{ dictConvert('PayNotifyProcess', form.status) }}
</a-descriptions-item>
<a-descriptions-item label="提示消息">
{{ form.msg }}

View File

@@ -69,6 +69,8 @@ export interface Payment extends BaseEntity {
payTypeInfo?: string
// 是否是异步支付
asyncPayMode?: boolean
// 是否是组合支付
combinationPayMode?: boolean
// 异步支付方式
asyncPayChannel?: number
// 支付通道信息列表

View File

@@ -28,9 +28,12 @@
<a-descriptions-item label="支付状态">
{{ dictConvert('PayStatus', form.payStatus) }}
</a-descriptions-item>
<a-descriptions-item label="是否是异步支付">
<a-descriptions-item label="异步支付">
{{ form.asyncPayMode ? '是' : '否' }}
</a-descriptions-item>
<a-descriptions-item label="组合支付">
{{ form.combinationPayMode ? '是' : '否' }}
</a-descriptions-item>
<a-descriptions-item label="异步支付方式">
{{ dictConvert('PayChannel', form.asyncPayChannel) }}
</a-descriptions-item>

View File

@@ -35,11 +35,16 @@
{{ dictConvert('PayStatus', row.payStatus) }}
</template>
</vxe-column>
<vxe-column field="asyncPayMode" title="是否是异步支付">
<vxe-column field="asyncPayMode" title="异步支付">
<template #default="{ row }">
{{ row.asyncPayMode ? '是' : '否' }}
</template>
</vxe-column>
<vxe-column field="combinationPayMode" title="组合支付">
<template #default="{ row }">
{{ row.combinationPayMode ? '是' : '否' }}
</template>
</vxe-column>
<vxe-column field="asyncPayChannel" title="异步支付方式">
<template #default="{ row }">
{{ dictConvert('PayChannel', row.asyncPayChannel) }}
@@ -59,10 +64,12 @@
<a-menu-item>
<a-link @click="sync(row)">刷新信息</a-link>
</a-menu-item>
<a-menu-item v-if="[0].includes(row.payStatus)">
<a-link @click="remove(row)" danger>关闭</a-link>
<a-menu-item v-if="[PayStatus.TRADE_PROGRESS].includes(row.payStatus)">
<a-link @click="cancel(row)" danger>关闭</a-link>
</a-menu-item>
<a-menu-item v-if="[1, 4].includes(row.payStatus) && row.refundableBalance > 0">
<a-menu-item
v-if="[PayStatus.TRADE_SUCCESS, PayStatus.TRADE_REFUNDING].includes(row.payStatus) && row.refundableBalance > 0"
>
<a-link @click="refund(row)" danger>退款</a-link>
</a-menu-item>
</a-menu>
@@ -91,14 +98,16 @@
import { page, superPage } from './Payment.api'
import useTablePage from '/@/hooks/bootx/useTablePage'
import PaymentInfo from './PaymentInfo.vue'
import { VxeTableInstance, VxeToolbarInstance } from 'vxe-table'
import RefundModel from './RefundModel.vue'
import BQuery from '/@/components/Bootx/Query/BQuery.vue'
import { useMessage } from '/@/hooks/web/useMessage'
import { BOOLEAN, DATE_TIME, LIST, NUMBER, QueryField, QueryParam, STRING } from '/@/components/Bootx/Query/Query'
import { useDict } from '/@/hooks/bootx/useDict'
import { cancelByPaymentId, syncByBusinessId } from '/@/api/common/Pay'
import BSuperQuery from '/@/components/Bootx/SuperQuery/BSuperQuery.vue'
import { VxePager, VxeTable, VxeToolbar } from "../../../../../../dist/assets/index.669df1fd";
import { VxeTableInstance, VxeToolbarInstance, VxePager, VxeTable, VxeToolbar } from 'vxe-table'
import ALink from '/@/components/Link/Link.vue'
import { PayStatus } from '/@/enums/payment/PayStatus'
// 使用hooks
const { handleTableChange, pageQueryResHandel, resetQuery, resetQueryParams, pagination, pages, model, loading, superQueryFlag } =
@@ -118,7 +127,7 @@
const queryFields = [
{ field: 'id', name: '支付ID', type: STRING },
{ field: 'businessId', name: '业务ID', type: STRING },
{ field: 'mchCode', type: STRING, name: '商户编码'},
{ field: 'mchCode', type: STRING, name: '商户编码' },
{ field: 'mchAppCode', type: STRING, name: '应用号编码' },
{ field: 'userId', name: '用户ID', type: STRING },
{ field: 'title', name: '标题', type: STRING },
@@ -157,7 +166,9 @@
}
}
// 分页查询
/**
* 分页查询
*/
function queryPage() {
loading.value = true
page({
@@ -169,18 +180,24 @@
})
}
// 排序条件变动
/**
* 排序条件变动
*/
function sortChange({ order, property }) {
sortParam.sortField = order ? property : null
sortParam.asc = order === 'asc'
init()
}
// 超级查询条件变动
/**
* 超级查询条件变动
*/
function changeSuperQuery(queryParams) {
superQueryParam = queryParams
superQuery()
}
// 超级查询
/**
* 超级查询
*/
function superQuery() {
superQueryFlag.value = true
loading.value = true
@@ -192,12 +209,16 @@
})
}
// 查看
/**
* 查看
*/
function show(record) {
paymentInfo.init(record.id)
}
// 同步信息
/**
* 同步信息
*/
function sync(record) {
loading.value = true
syncByBusinessId(record.businessId).then(() => {
@@ -205,7 +226,9 @@
queryPage()
})
}
// 关闭支付
/**
* 关闭支付
*/
function cancel(record) {
createConfirm({
iconType: 'warning',

View File

@@ -8,15 +8,19 @@
:mask-closable="showable"
@cancel="handleCancel"
>
<a-spin :spinning="confirmLoading">
<a-form ref="formRef" :model="form" :label-col="labelCol" :wrapper-col="wrapperCol">
<template :key="o.payChannel" v-for="o in form.refundModeParams">
<a-form-item :label="dictConvert('PayChannel', o.payChannel)" name="name">
<a-input-number :min="0.01" :max="o.amount" :precision="2" v-model:value="o.amount" />
</a-form-item>
</template>
</a-form>
</a-spin>
<a-form ref="formRef" :model="form" :label-col="labelCol" :wrapper-col="wrapperCol">
<template :key="o.payChannel" v-for="o in form.refundModeParams">
<a-form-item :label="dictConvert('PayChannel', o.payChannel)" name="name">
<a-input-number :min="0.01" :max="o.amount" :precision="2" v-model:value="o.amount" />
</a-form-item>
</template>
</a-form>
<template #footer>
<a-space>
<a-button key="cancel" @click="handleCancel">取消</a-button>
<a-button key="forward" :loading="confirmLoading" type="primary" @click="handleOk">确定</a-button>
</a-space>
</template>
</basic-modal>
</template>
@@ -66,6 +70,10 @@
confirmLoading.value = false
})
}
/**
* 退款
*/
function handleOk() {
createConfirm({
iconType: 'warning',
@@ -80,7 +88,6 @@
})
},
})
confirmLoading.value = true
}
defineExpose({ init })
</script>

View File

@@ -31,7 +31,7 @@
{{ form.refundTime }}
</a-descriptions-item>
<a-descriptions-item label="退款状态">
<a-tag>{{ form.refundStatus ? '成功' : '失败' }}</a-tag>
<a-tag>{{ dictConvert('PayRefundProcess', form.refundStatus) }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="退款终端ip">
{{ form.clientIp }}

View File

@@ -21,7 +21,7 @@
<vxe-column field="refundTime" title="退款时间" />
<vxe-column field="refundStatus" title="状态">
<template #default="{ row }">
<a-tag>{{ row.refundStatus ? '成功' : '失败' }}</a-tag>
<a-tag>{{ dictConvert('PayRefundProcess', row.refundStatus) }}</a-tag>
</template>
</vxe-column>
<vxe-column fixed="right" width="60" :showOverflow="false" title="操作">

View File

@@ -8,7 +8,7 @@
<template #buttons>
<a-space>
<a-button type="primary" pre-icon="ant-design:plus-outlined" @click="add">新建</a-button>
<a-button pre-icon="ant-design:sync-outlined" @click="syncJobStatus">状态同步</a-button>
<a-button pre-icon="ant-design:sync-outlined" @click="sync">状态同步</a-button>
</a-space>
</template>
</vxe-toolbar>
@@ -204,6 +204,7 @@
// 同步任务状态
function sync() {
syncJobStatus().then(() => {
queryPage()
createMessage.success('任务状态同步成功')
})
}