feat 项目更新

This commit is contained in:
DaxPay
2025-03-27 10:30:23 +08:00
parent a96441949a
commit fef2335bc3
46 changed files with 2203 additions and 2630 deletions

10
.env
View File

@@ -1,12 +1,12 @@
# port
VITE_PORT = 9100
VITE_PORT=9100
# spa-title
VITE_GLOB_APP_TITLE = DaxPay
VITE_GLOB_APP_TITLE=DaxPay
# 系统中文名称
VITE_GLOB_APP_TITLE_CN = DaxPay支付系统
VITE_GLOB_APP_TITLE_CN=DaxPay支付系统
# spa shortname 不可出现空格数字等特殊字符
VITE_GLOB_APP_SHORT_NAME = vantMobile
# spa shortname 不可出现空格数字连接线等特殊字符
VITE_GLOB_APP_SHORT_NAME=DaxPay

View File

@@ -5,7 +5,7 @@ VITE_PORT=9100
VITE_PUBLIC_PATH=/
# 跨域代理,可以配置多个,请注意不要换行
VITE_PROXY=[["/server","http://localhost:10880"]]
VITE_PROXY=[["/server","http://localhost:19999"]]
# API 接口地址
VITE_GLOB_API_URL=

View File

@@ -1,12 +1,8 @@
# 独立部署模式
VITE_PUBLIC_PATH = /
# API 接口前缀
VITE_GLOB_API_URL_PREFIX = /api
# 嵌入式模式 与网关部署在一起, 和 接口 (server) 前缀 这个是
#VITE_PUBLIC_PATH=/h5
## API 接口前缀
#VITE_GLOB_API_URL_PREFIX=
# 项目访问目录
VITE_PUBLIC_PATH=/
# API 接口前缀
VITE_GLOB_API_URL_PREFIX=
# 是否删除console
VITE_DROP_CONSOLE=true

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 傲慢或香橙
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,55 +1,16 @@
<p align="center">
<img src="_doc/images/dax-pay.svg" width="45%">
</p>
# DaxPay移动H5端
# DaxPay H5端
## 简绍
vite5 + vue3.4 + vben admin2 + antd vue4 搭建
## 🍈项目介绍
## 环境依赖
> axPay是一套开源支付网关系统已经对接支付宝、微信支付、云闪付相关的接口。可以独立部署提供接口供业务系统进行调用不对原有系统产生影响
| 环境 | 版本 | 备注 |
|------|-----------|--------------------------|
| Node | >=20.9.0 | 低版本会无法进行构建 |
| pnpm | >=9.0.2 | 低版本会导致 pnpm-lock.yaml 失效 |
## 🍒 项目地址
| 项目 | GITEE | GITHUB |
| ----------- | ------------------------------------------- | ----------------------------------------------- |
| 后端地址 | [GITEE](https://gitee.com/dromara/dax-pay) | [GITHUB](https://github.com/dromara/dax-pay) |
| Web前端地址 | [GITEE](https://gitee.com/bootx/dax-pay-ui) | [GITHUB](https://github.com/xxm1995/dax-pay-ui) |
| H5前端地址 | [GITEE](https://gitee.com/bootx/dax-pay-h5) | [GITHUB](https://github.com/xxm1995/dax-pay-h5) |
## 🏬 系统演示
### 管理平台:
> 注:演示账号部分功能修改删除权限未开放。
地址https://daxpay.demo.bootx.cn
账号daxpay
密码123456
### 网关接口
> 注:接口平台只开放支付网关相关的接口,不开放系统其他接口。
地址: https://daxpay.server.bootx.cn/doc.html
账号: daxpay
密码: 123456
### 收银台演示
> 请勿大额支付,可以通过后台管理端进行退款
电脑收银台地址: https://daxpay.demo.bootx.cn/#/cashier
手机收银台地址: https://daxpay.demo.bootx.cn/h5/#/cashier/uniCashier
## 项目启动
- 必须使用 [pnpm>=8.6.10](https://www.pnpm.cn/),否则依赖可能安装不上,推荐使用`8.6.10`
- [Node.js](http://nodejs.org/) 版本要求`18.x`以上,推荐使用 `^20.9.0`
## 启动命令
```shell
# 安装项目依赖
@@ -60,6 +21,6 @@ pnpm run dev
pnpm build
```
## 🍷License
## License
Apache License Version 2.0

21
components.d.ts vendored
View File

@@ -1,21 +0,0 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
declare module 'vue' {
export interface GlobalComponents {
Loading: typeof import('./src/components/Loading.vue')['default']
Logo: typeof import('./src/components/Logo.vue')['default']
SvgIcon: typeof import('./src/components/SvgIcon.vue')['default']
VanButton: typeof import('vant/es')['Button']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanDialog: typeof import('vant/es')['Dialog']
VanField: typeof import('vant/es')['Field']
VanLoading: typeof import('vant/es')['Loading']
VanNumberKeyboard: typeof import('vant/es')['NumberKeyboard']
VanTextEllipsis: typeof import('vant/es')['TextEllipsis']
}
}

View File

@@ -1,9 +1,9 @@
{
"name": "daxpay-h5",
"name": "daxpay-gateway-ui",
"type": "module",
"version": "3.0.0",
"engines": {
"node": ">=18.12.0",
"node": ">=20.6.0",
"pnpm": ">=9.0.2"
},
"scripts": {
@@ -27,62 +27,62 @@
"@types/lodash-es": "^4.17.12",
"@unocss/reset": "^0.58.9",
"@vueuse/core": "^10.11.1",
"axios": "^1.7.7",
"axios": "^1.7.9",
"date-fns": "^3.6.0",
"lodash-es": "^4.17.21",
"nprogress": "^0.2.0",
"pinia": "^2.2.2",
"pinia": "^2.3.0",
"pinia-plugin-persist": "^1.0.0",
"qs": "^6.13.0",
"vant": "^4.9.7",
"vue": "^3.5.8",
"qs": "^6.13.1",
"vant": "^4.9.10",
"vue": "^3.5.13",
"vue-router": "4.2.5"
},
"devDependencies": {
"@antfu/eslint-config": "^2.27.3",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"@iconify/json": "^2.2.252",
"@iconify/json": "^2.2.281",
"@types/fs-extra": "^11.0.4",
"@types/mockjs": "^1.0.10",
"@types/node": "^20.16.5",
"@types/node": "^20.17.9",
"@types/nprogress": "^0.2.3",
"@types/qs": "^6.9.16",
"@types/qs": "^6.9.17",
"@unocss/eslint-plugin": "^0.58.9",
"@unocss/preset-icons": "^0.58.9",
"@unocss/preset-rem-to-px": "^0.58.9",
"@unocss/transformer-directives": "^0.58.9",
"@unocss/transformer-variant-group": "^0.58.9",
"@vitejs/plugin-vue": "^5.1.4",
"@vitejs/plugin-vue": "^5.2.1",
"autoprefixer": "^10.4.20",
"cross-env": "^7.0.3",
"cz-git": "^1.9.4",
"dotenv": "^16.4.5",
"cz-git": "^1.11.0",
"dotenv": "^16.4.7",
"eslint": "^8.57.1",
"eslint-plugin-format": "^0.1.2",
"eslint-plugin-format": "^0.1.3",
"esno": "^0.16.3",
"fs-extra": "^11.2.0",
"less": "^4.2.0",
"less": "^4.2.1",
"lint-staged": "^15.2.10",
"only-allow": "^1.2.1",
"picocolors": "^1.1.0",
"postcss": "^8.4.47",
"postcss-mobile-forever": "^4.1.6",
"picocolors": "^1.1.1",
"postcss": "^8.4.49",
"postcss-mobile-forever": "^4.3.1",
"rimraf": "^3.0.2",
"rollup": "^4.22.4",
"rollup": "^4.28.1",
"rollup-plugin-visualizer": "^5.12.0",
"simple-git-hooks": "^2.11.1",
"typescript": "^5.6.2",
"typescript": "^5.7.2",
"unocss": "^0.58.9",
"unplugin-auto-import": "^0.17.8",
"unplugin-vue-components": "^0.26.0",
"vconsole": "^3.15.1",
"vite-plugin-vconsole": "^2.1.1",
"vite": "5.2.6",
"vite": "5.4.6",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-mock": "^2.9.8",
"vite-plugin-svg-icons": "^2.0.1",
"vite-plugin-vconsole": "^2.1.1",
"vue-tsc": "^1.8.27"
},
"lint-staged": {

2641
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
<template>
<vanConfigProvider :theme="getDarkMode" :theme-vars="getThemeVars()">
<vanConfigProvider :theme="darkMode" :theme-vars="getThemeVars()">
<routerView v-slot="{ Component }">
<div class="absolute bottom-0 top-0 w-full overflow-hidden">
<transition :name="getTransitionName" mode="out-in" appear>
@@ -18,8 +18,10 @@ import { useRouteStore } from '@/store/modules/route'
import { useDesignSetting } from '@/hooks/setting/useDesignSetting'
const routeStore = useRouteStore()
const { getDarkMode, getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting()
const { getAppTheme, getIsPageAnimate, getPageAnimateType } = useDesignSetting()
// 读取是否为夜间模式
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
const darkMode = mediaQuery.matches ? 'dark' : 'light'
// 需要缓存的路由组件
const keepAliveComponents = computed(() => routeStore.keepAliveComponents)

View File

@@ -10,7 +10,7 @@ export enum ChannelEnum {
/**
* 支付方式
*/
export enum payMethodEnum {
export enum PayMethodEnum {
WAP = 'wap',
APP = 'app',
WEB = 'web',
@@ -26,3 +26,21 @@ export enum CashierTypeEnum {
WECHAT_PAY = 'wechat_pay',
ALIPAY = 'alipay',
}
/**
* 收银台类型
*/
export enum CheckoutTypeEnum {
H5 = 'h5',
PC = 'pc',
MINI_APP = 'mini_app',
AGGREGATE = 'aggregate',
}
/**
* 收银台聚合支付类型
*/
export enum CheckoutAggregateEnum {
ALI = 'alipay',
WECHAT = 'wechat_pay',
}

View File

@@ -4,8 +4,6 @@ import { useDesignSettingStore } from '@/store/modules/designSetting'
export function useDesignSetting() {
const designStore = useDesignSettingStore()
const getDarkMode = computed(() => designStore.darkMode)
const getAppTheme = computed(() => designStore.appTheme)
const getAppThemeList = computed(() => designStore.appThemeList)
@@ -15,7 +13,6 @@ export function useDesignSetting() {
const getPageAnimateType = computed(() => designStore.pageAnimateType)
return {
getDarkMode,
getAppTheme,
getAppThemeList,
getIsPageAnimate,

View File

@@ -50,3 +50,31 @@ export const routeModuleList: Array<RouteRecordRaw> = [
],
},
]
/**
* 业务模块路由
*/
export const BusinessRoute: RouteRecordRaw = {
path: '/',
name: '',
redirect: PageEnum.BASE_HOME,
component: Layout,
children: [
{
path: '/result/success',
name: 'SuccessResult',
component: () => import('@/views/result/SuccessResult.vue'),
meta: {
title: '操作成功',
},
},
{
path: '/result/error',
name: 'ErrorResult',
component: () => import('@/views/result/ErrorResult.vue'),
meta: {
title: '错误页',
},
},
],
}

View File

@@ -1,40 +0,0 @@
import type { RouteRecordRaw } from 'vue-router'
import { PageEnum } from '@/enums/pageEnum'
const Layout = () => import('@/layout/index.vue')
/**
* 业务模块路由
*/
export const BusinessRoute: RouteRecordRaw = {
path: '/',
name: '',
redirect: PageEnum.BASE_HOME,
component: Layout,
children: [
{
path: '/t/:key',
name: 'DispatchRouter',
component: () => import('@/views/system/dispatch/DispatchRouter.vue'),
meta: {
title: '中转页',
},
},
{
path: '/result/success',
name: 'SuccessResult',
component: () => import('@/views/result/SuccessResult.vue'),
meta: {
title: '操作成功',
},
},
{
path: '/result/error',
name: 'ErrorResult',
component: () => import('@/views/result/ErrorResult.vue'),
meta: {
title: '错误页',
},
},
],
}

View File

@@ -11,41 +11,49 @@ export const DaxPayRoute: RouteRecordRaw = {
component: Layout,
children: [
{
path: '/alipay/auth/:appId/:channel/:queryCode/:aliAppId',
path: '/auth/alipay/:appId/:channel/:queryCode/:aliAppId',
name: 'AlipayAuth',
component: () => import('@/views/daxpay/channel/alipay/auth/AlipayAuth.vue'),
component: () => import('@/views/daxpay/auth/alipay/AlipayAuth.vue'),
meta: {
title: '支付宝信息获取',
},
},
{
path: '/wechat/auth/:appId/:channel/:queryCode',
path: '/auth/wechat/:appId/:channel/:queryCode',
name: 'WechatAuth',
component: () => import('@/views/daxpay/channel/wechat/auth/WechatAuth.vue'),
component: () => import('@/views/daxpay/auth/wechat/WechatAuth.vue'),
meta: {
title: '微信信息获取',
},
},
{
path: '/channel/cashier/:mchNo/:appId',
name: 'ChannelCashier',
component: () => import('@/views/daxpay/channel/ChannelCashier.vue'),
path: '/cashier/:code',
name: 'CashierCode',
component: () => import('@/views/daxpay/cashier/Cashier.vue'),
meta: {
title: '收银台',
},
},
{
path: '/alipay/cashier/:mchNo/:appId',
name: 'AlipayCashier',
component: () => import('@/views/daxpay/channel/alipay/cashier/AlipayCashier.vue'),
path: '/aggregate/:code',
name: 'CheckAggregate',
component: () => import('@/views/daxpay/aggregate/Aggregate.vue'),
meta: {
title: '聚合收银台',
},
},
{
path: '/aggregate/alipay/:code',
name: 'AliCheckout',
component: () => import('@/views/daxpay/aggregate/alipay/AlipayAggregate.vue'),
meta: {
title: '支付宝收银台',
},
},
{
path: '/wechat/cashier/:mchNo/:appId',
name: 'WechatCashier',
component: () => import('@/views/daxpay/channel/wechat/cashier/WechatCashier.vue'),
path: '/aggregate/wechat/:code',
name: 'WechatCheckout',
component: () => import('@/views/daxpay/aggregate/wechat/WechatAggregate.vue'),
meta: {
title: '微信收银台',
},

View File

@@ -1,12 +1,10 @@
import type { App } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import { createRouter, createWebHashHistory, createWebHistory } from "vue-router";
import { createRouterGuards } from './router-guards'
import { ErrorPageRoute, routeModuleList } from '@/router/base'
import { createRouter, createWebHistory } from 'vue-router'
import { BusinessRoute, ErrorPageRoute, routeModuleList } from '@/router/base'
import { useRouteStoreWidthOut } from '@/store/modules/route'
// 菜单
import { BusinessRoute } from '@/router/business'
import { DaxPayRoute } from '@/router/daxpay'
// 普通路由
@@ -23,7 +21,7 @@ routeStore.setRouters(constantRouter.concat(routeModuleList))
const router = createRouter({
// 重定向时hash模式有场景无法跳转需要使用history模式
history: createWebHistory(''),
history: createWebHistory(import.meta.env.VITE_PUBLIC_PATH),
routes: constantRouter.concat(...routeModuleList),
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),
@@ -31,7 +29,6 @@ const router = createRouter({
export function setupRouter(app: App) {
app.use(router)
createRouterGuards(router)
}
export default router

View File

@@ -1,49 +0,0 @@
import type { Router } from 'vue-router'
import { isNavigationFailure } from 'vue-router'
import { useRouteStoreWidthOut } from '@/store/modules/route'
export function createRouterGuards(router: Router) {
router.beforeEach(async (to, from, next) => {
// 所有的的页面都可以访问
next()
})
// 进入某个路由之后触发的钩子
router.afterEach((to, _, failure) => {
// 设置每个页面的 title
document.title = (to?.meta?.title as string) || document.title
if (isNavigationFailure(failure)) {
console.log('failed navigation', failure)
}
const routeStore = useRouteStoreWidthOut()
// 在这里设置需要缓存的组件名称
const keepAliveComponents = routeStore.keepAliveComponents
// 获取当前组件名
const currentComName: any = to.matched.find(item => item.name == to.name)?.name
// 如果 currentComName 且 keepAliveComponents 不包含 currentComName 且 即将要进入的路由 meta 属性里 keepAlive 为 true则缓存该组件
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件
keepAliveComponents.push(currentComName)
// keepAlive 为 false 则不缓存
}
else if (!to.meta?.keepAlive) {
// 不需要缓存的组件
// 这里的作用一开始组件设置为缓存,之后又设置不缓存但是它还是存在 keepAliveComponents 数组中
// keepAliveComponents 使用 findIndex 与 当前路由对比,如果存在则返回具体下标位置,不存在返回 -1
const index = routeStore.keepAliveComponents.findIndex(name => name == currentComName)
if (index != -1) {
// 通过返回具体下标位置删除 keepAliveComponents 数组中缓存的 元素
keepAliveComponents.splice(index, 1)
}
}
routeStore.setKeepAliveComponents(keepAliveComponents)
})
router.onError((error) => {
console.error(error, '路由错误')
})
}

View File

@@ -1,8 +1,6 @@
// app theme preset color
export interface DesignSettingState {
// 系统主题
darkMode: 'light' | 'dark'
// 系统风格
appTheme: string
// 系统内置风格
@@ -36,8 +34,6 @@ export const appThemeList: string[] = [
]
const setting: DesignSettingState = {
// 深色主题
darkMode: 'dark',
// 系统主题色
appTheme: '#5d9dfe',
// 系统内置主题色列表

View File

@@ -3,21 +3,17 @@ import { store } from '@/store'
import designSetting from '@/settings/designSetting'
import type { DesignSettingState } from '@/settings/designSetting'
const { darkMode, appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
const { appTheme, appThemeList, isPageAnimate, pageAnimateType } = designSetting
export const useDesignSettingStore = defineStore({
id: 'app-design-setting',
state: (): DesignSettingState => ({
darkMode,
appTheme,
appThemeList,
isPageAnimate,
pageAnimateType,
}),
getters: {
getDarkMode(): 'light' | 'dark' {
return this.darkMode
},
getAppTheme(): string {
return this.appTheme
},
@@ -32,9 +28,6 @@ export const useDesignSettingStore = defineStore({
},
},
actions: {
setDarkMode(mode: 'light' | 'dark'): void {
this.darkMode = mode
},
setPageAnimateType(type: string): void {
this.pageAnimateType = type
},

View File

@@ -9,3 +9,10 @@ body {
.van-action-sheet__cancel {
color: var(--van-primary-color) !important;
}
.loading-wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}

View File

@@ -3,7 +3,7 @@
<div class="wel-box w-full flex flex-col items-center justify-between">
<SvgIcon class="logo" :size="130" name="logo" />
<div class="text-darkBlue dark:text-garyWhite mb-4 mt-12 text-center text-2xl font-black">
欢迎使用DaxPay
DaxPay支付网关
</div>
</div>
</div>

View File

@@ -1,35 +1,24 @@
import type { AuthResult } from './ChannelAuth.api'
import type { AuthResult } from '../auth/ChannelAuth.api'
import { http } from '@/utils/http/axios'
import type { Result } from '#/axios'
/**
*
*
*/
export function getMchName(mchNo: string) {
return http.request<Result<string>>({
url: '/unipay/ext/channel/cashier/getMchName',
export function getCashierTypeConfig(cashierType: string, cashierCode: string) {
return http.request<Result<CashierTypeConfigResult>>({
url: '/unipay/cashier/code/findByCashierType',
method: 'GET',
params: { mchNo },
})
}
/**
*
*/
export function getCashierInfo(cashierType: string, appId: string) {
return http.request<Result<ChannelCashierConfigResult>>({
url: '/unipay/ext/channel/cashier/getCashierType',
method: 'GET',
params: { cashierType, appId },
params: { cashierType, cashierCode },
})
}
/**
* , OpenId一类的信息
*/
export function generateAuthUrl(param: CashierAuthParam) {
export function generateAuthUrl(param: CashierAuthUrlParam) {
return http.request<Result<string>>({
url: '/unipay/ext/channel/cashier/generateAuthUrl',
url: '/unipay/cashier/code/generateAuthUrl',
method: 'POST',
data: param,
})
@@ -38,9 +27,9 @@ export function generateAuthUrl(param: CashierAuthParam) {
/**
*
*/
export function auth(param: CashierPayParam) {
export function auth(param: CashierAuthParam) {
return http.request<Result<AuthResult>>({
url: '/unipay/ext/channel/cashier/auth',
url: '/unipay/cashier/code/auth',
method: 'POST',
data: param,
})
@@ -51,20 +40,28 @@ export function auth(param: CashierPayParam) {
*/
export function cashierPay(param: CashierPayParam) {
return http.request<Result<PayResult>>({
url: '/unipay/ext/channel/cashier/pay',
url: '/unipay/cashier/code/pay',
method: 'POST',
data: param,
})
}
/**
*
* URL参
*/
export interface CashierAuthUrlParam {
// 应用号
cashierCode?: string
// 收银台类型
cashierType?: string
}
/**
*
*/
export interface CashierAuthParam {
// 商户号
mchNo?: string
// 应用号
appId?: string
cashierCode?: string
// 收银台类型
cashierType?: string
// 授权码
@@ -75,11 +72,8 @@ export interface CashierAuthParam {
*
*/
export interface CashierPayParam {
// 商户号
mchNo?: string
// 应用号
appId?: string
cashierCode?: string
// 收银台类型
cashierType?: string
// 支付金额
@@ -94,7 +88,6 @@ export interface CashierPayParam {
*
*/
export interface PayResult {
// 支付状态
status: string
// 支付参数体
@@ -124,23 +117,17 @@ export interface WxJsapiSignResult {
}
/**
*
*
*/
export interface ChannelCashierConfigResult {
// 商户号
mchNo?: string
export interface CashierTypeConfigResult {
// 应用号
appId?: string
// 收银台类型
cashierType?: string
// 收银台名称
cashierName?: string
// 码牌名称
name?: string
// 支付通道
channel?: string
// 支付方式
payMethod?: string
// 是否开启分账
allocation?: boolean
// 自动分账
autoAllocation?: boolean
}

View File

@@ -6,17 +6,17 @@ import { useRoute } from 'vue-router'
import router from '@/router'
const route = useRoute()
const { mchNo, appId } = route.params
const { code } = route.params
const ua = navigator.userAgent
if (ua.includes('MicroMessenger')) {
router.push({ path: `/wechat/cashier/${mchNo}/${appId}`, replace: true })
router.push({ path: `/aggregate/wechat/${code}`, replace: true })
}
else if (ua.includes('Alipay')) {
router.push({ path: `/alipay/cashier/${mchNo}/${appId}`, replace: true })
router.push({ path: `/aggregate/alipay/${code}`, replace: true })
}
else {
router.push({ name: 'ErrorResult', query: { msg: '请使用支付宝、微信等支付程序进行扫码支付' }, replace: true })
router.push({ name: 'ErrorResult', query: { msg: '请使用支付宝、微信等软件进行扫码支付' }, replace: true })
}
</script>

View File

@@ -1,11 +1,11 @@
<template>
支付超时
</template>
<script setup lang="ts">
</script>
<template>
</template>
<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

@@ -1,6 +1,6 @@
<template>
<van-overlay v-show="show" :show="true">
<div class="wrapper" @click.stop>
<div class="loading-wrapper" @click.stop>
<van-loading size="24px">
获取中...
</van-loading>
@@ -12,8 +12,8 @@
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import { showDialog } from 'vant'
import type { AuthCodeParam } from '@/views/daxpay/channel/ChannelAuth.api'
import { authAndSet } from '@/views/daxpay/channel/ChannelAuth.api'
import type { AuthCodeParam } from '@/views/daxpay/auth/ChannelAuth.api'
import { authAndSet } from '@/views/daxpay/auth/ChannelAuth.api'
const script = document.createElement('script')
script.setAttribute(
@@ -60,12 +60,6 @@ async function init() {
</script>
<style scoped lang="less">
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.block {
width: 120px;

View File

@@ -1,6 +1,6 @@
<template>
<van-overlay v-show="show" :show="true">
<div class="wrapper" @click.stop>
<div class="loading-wrapper" @click.stop>
<van-loading size="24px">
获取中...
</van-loading>
@@ -12,8 +12,8 @@
import { useRoute } from 'vue-router'
import { onMounted, ref } from 'vue'
import { showDialog } from 'vant'
import type { AuthCodeParam } from '@/views/daxpay/channel/ChannelAuth.api'
import { authAndSet } from '@/views/daxpay/channel/ChannelAuth.api'
import type { AuthCodeParam } from '@/views/daxpay/auth/ChannelAuth.api'
import { authAndSet } from '@/views/daxpay/auth/ChannelAuth.api'
const route = useRoute()
const { appId, channel, queryCode } = route.params
@@ -47,12 +47,6 @@ async function init() {
</script>
<style scoped lang="less">
.wrapper {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
.block {
width: 120px;

View File

View File

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

View File

@@ -1,165 +0,0 @@
<template>
<div>
<div class="container">
<div style="font-size: 28px;margin-top: 10px;">
{{ cashierInfo.cashierName || '支付宝收银台' }}
</div>
<div class="amount-display">
<p style="font-size: 20px">
付款给{{ mchName }}
</p>
<p style="font-size: 32px;">
¥ {{ amount }}
</p>
</div>
</div>
<van-dialog
v-model:show="showRemark"
title="支付备注"
confirm-button-text="保存"
cancel-button-text="清除"
confirm-button-color="#108ee9"
cancel-button-color="red"
show-cancel-button
@cancel="description = ''"
>
<van-field
v-model="description"
rows="2"
autosize
label=""
type="textarea"
:maxlength="50"
placeholder="请输入支付备注内容"
show-word-limit
/>
</van-dialog>
<van-number-keyboard
:show="true"
theme="custom"
extra-key="."
close-button-text="付款"
@close="pay"
@input="input"
@delete="del"
>
<template #title-left>
<div style="width: 100vw;display: flex; justify-content: center">
<div class="remark" @click="showRemark = true">
<div v-if="!description">
添加备注
</div>
<div v-else style="max-width: 75vw;">
<van-text-ellipsis :content="`备注: ${description}`" /><div />
</div>
</div>
</div>
</template>
</van-number-keyboard>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { showNotify } from 'vant'
import type {
CashierPayParam,
ChannelCashierConfigResult,
} from '@/views/daxpay/channel/ChannelCashier.api'
import {
cashierPay,
getCashierInfo,
getMchName,
} from '@/views/daxpay/channel/ChannelCashier.api'
import { CashierTypeEnum } from '@/enums/daxpay/DaxPayEnum'
import router from '@/router'
import { useKeyboard } from '@/hooks/daxpay/useKeyboard'
const route = useRoute()
const { mchNo, appId } = route.params
const showRemark = ref<boolean>(false)
const loading = ref<boolean>(false)
const cashierInfo = ref<ChannelCashierConfigResult>({})
const amount = ref<string>('0')
const description = ref<string>('')
const mchName = ref<string>('')
const { input, del } = useKeyboard(amount)
onMounted(() => {
initData()
})
/**
* 初始化数据
*/
function initData() {
getCashierInfo(CashierTypeEnum.ALIPAY, appId as string).then(({ data }) => {
cashierInfo.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message } })
})
getMchName(mchNo as string).then(({ data }) => {
mchName.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message } })
})
}
/**
* 支付
*/
function pay() {
const amountValue = Number(amount.value)
if (amountValue === 0) {
showNotify({ type: 'warning', message: '金额不可为0' })
return
}
loading.value = true
const from = {
amount: amountValue,
appId,
cashierType: CashierTypeEnum.ALIPAY,
description: description.value,
mchNo,
} as CashierPayParam
cashierPay(from)
.then(({ data }) => {
loading.value = false
// 跳转到H5/付款码支付页面
location.replace(data.payBody)
})
}
</script>
<style scoped lang="less">
@color: #108ee9;
:deep(.van-key--blue) {
background: @color;
}
.container {
background-color: @color;
width: 100%;
padding: 10px;
border-radius: 10px;
text-align: center;
color: white;
.amount-display {
background-color: white;
color: @color;
border-radius: 20px;
padding: 20px;
margin: 20px 0;
}
.amount-display p {
margin: 5px 0;
}
}
.remark {
color: @color;
}
</style>

View File

@@ -1,230 +0,0 @@
<template>
<div v-if="show">
<div class="container">
<div style="font-size: 28px;margin-top: 10px;">
{{ cashierInfo.cashierName || '微信收银台' }}
</div>
<div class="amount-display">
<p style="font-size: 20px">
付款给{{ mchName }}
</p>
<p style="font-size: 32px;">
¥ {{ amount }}
</p>
</div>
</div>
<van-dialog
v-model:show="showRemark"
title="支付备注"
confirm-button-text="保存"
cancel-button-text="清除"
confirm-button-color="#4CAF50"
cancel-button-color="red"
show-cancel-button
@cancel="description = ''"
>
<van-field
v-model="description"
rows="2"
autosize
label=""
type="textarea"
:maxlength="50"
placeholder="请输入支付备注内容"
show-word-limit
/>
</van-dialog>
<van-number-keyboard
:show="true"
theme="custom"
extra-key="."
close-button-text="付款"
@close="pay"
@input="input"
@delete="del"
>
<template #title-left>
<div style="width: 100vw;display: flex; justify-content: center">
<div class="remark" @click="showRemark = true">
<div v-if="!description">
添加备注
</div>
<div v-else style="max-width: 75vw;">
<van-text-ellipsis :content="`备注: ${description}`" /><div />
</div>
</div>
</div>
</template>
</van-number-keyboard>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { showNotify } from 'vant'
import type {
CashierAuthParam,
CashierPayParam,
ChannelCashierConfigResult,
WxJsapiSignResult,
} from '@/views/daxpay/channel/ChannelCashier.api'
import {
auth
, cashierPay,
generateAuthUrl,
getCashierInfo,
getMchName,
} from '@/views/daxpay/channel/ChannelCashier.api'
import { CashierTypeEnum } from '@/enums/daxpay/DaxPayEnum'
import router from '@/router'
import { useKeyboard } from '@/hooks/daxpay/useKeyboard'
const route = useRoute()
const { mchNo, appId } = route.params
const { code } = route.query
const show = ref<boolean>(false)
const showRemark = ref<boolean>(false)
const loading = ref<boolean>(false)
const cashierInfo = ref<ChannelCashierConfigResult>({})
const amount = ref<string>('0')
const description = ref<string>('')
const mchName = ref<string>('')
const openId = ref<string>('')
// 认证参数
const authParam = ref<CashierAuthParam>({
mchNo: mchNo as string,
appId: appId as string,
cashierType: CashierTypeEnum.WECHAT_PAY,
})
const { input, del } = useKeyboard(amount)
onMounted(() => {
init()
})
/**
* 初始化
*/
function init() {
// 如果不是重定向跳转过来, 跳转到到重定向地址
if (!code) {
// 重定向跳转到微信授权地址
generateAuthUrl(authParam.value).then((res) => {
const url = res.data
location.replace(url)
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message } })
})
}
else {
authParam.value.authCode = code as string
// 初始化数据
initData()
}
}
/**
* 初始化数据
*/
function initData() {
show.value = true
getCashierInfo(CashierTypeEnum.ALIPAY, appId as string).then(({ data }) => {
cashierInfo.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message } })
})
getMchName(mchNo as string).then(({ data }) => {
mchName.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message } })
})
auth(authParam.value).then(({ data }) => {
openId.value = data.openId as string
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message } })
})
}
/**
* 微信jsapi方式支付
*/
function pay() {
const amountValue = Number(amount.value)
if (amountValue === 0) {
showNotify({ type: 'warning', message: '金额不可为0' })
return
}
loading.value = true
const from = {
amount: amountValue,
appId,
openId: openId.value,
cashierType: CashierTypeEnum.WECHAT_PAY,
description: description.value,
mchNo,
} as CashierPayParam
cashierPay(from)
.then(({ data }) => {
loading.value = false
// 拉起jsapi支付
const json = JSON.parse(data.payBody)
jsapiPay(json)
})
}
/**
* 拉起Jsapi支付窗口
*/
function jsapiPay(data: WxJsapiSignResult) {
const form = {
appId: data.appId, // 公众号ID由商户传入
timeStamp: data.timeStamp, // 时间戳自1970年以来的秒数
nonceStr: data.nonceStr, // 随机串
package: data.package, // 预支付ID
signType: data.signType, // 微信签名方式:
paySign: data.paySign, // 微信签名
}
// 使用微信JsSdk拉起支付
WeixinJSBridge.invoke('getBrandWCPayRequest', form, (res) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 跳转到成功页面
router.push({ name: 'SuccessResult', query: { msg: '支付成功' }, replace: true })
}
})
}
</script>
<style scoped lang="less">
@color: #4caf50;
:deep(.van-key--blue) {
background: @color;
}
.container {
background-color: @color;
width: 100%;
padding: 10px;
border-radius: 10px;
text-align: center;
color: white;
.amount-display {
background-color: white;
color: @color;
border-radius: 20px;
padding: 20px;
margin: 20px 0;
}
.amount-display p {
margin: 5px 0;
}
}
.remark {
color: @color;
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import router from '@/router'
const route = useRoute()
const { orderNo } = route.params
const ua = navigator.userAgent
if (ua.includes('MicroMessenger')) {
router.push({ path: `/aggregate/wechat/${orderNo}`, replace: true })
}
else if (ua.includes('Alipay')) {
router.push({ path: `/aggregate/alipay/${orderNo}`, replace: true })
}
else {
router.push({ name: 'ErrorResult', query: { msg: '请使用支付宝、微信等软件进行扫码支付' }, replace: true })
}
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,213 @@
import { http } from '@/utils/http/axios'
import type { Result } from '#/axios'
import type { AuthResult } from '@/views/daxpay/auth/ChannelAuth.api'
import type { PayResult } from '@/views/daxpay/aggregate/Aggregate.api'
/**
* 获取收银台订单和配置信息
*/
export function getOrderAndConfig(orderNo, checkoutType) {
return http.request<Result<CheckoutOrderAndConfigResult>>({
url: '/unipay/checkout/getOrderAndConfig',
method: 'GET',
params: {
orderNo,
checkoutType,
},
})
}
/**
* 获取聚合支付配置
*/
export function getAggregateConfig(orderNo, aggregateType) {
return http.request<Result<AggregateOrderAndConfigResult>>({
url: '/unipay/checkout/getAggregateConfig',
method: 'GET',
params: {
orderNo,
aggregateType,
},
})
}
/**
* 获取收银台所需授权链接, 用于获取OpenId一类的信息
*/
export function generateAuthUrl(param: CheckoutAuthUrlParam) {
return http.request<Result<string>>({
url: '/unipay/checkout/generateAuthUrl',
method: 'post',
data: param,
})
}
/**
* 获取授权结果
*/
export function auth(param: CheckoutAuthCodeParam) {
return http.request<Result<AuthResult>>({
url: '/unipay/checkout/auth',
method: 'post',
data: param,
})
}
/**
* 发起普通支付
*/
export function checkoutPay(param: CheckoutPayParam) {
return http.request<Result<PayResult>>({
url: '/unipay/checkout/pay',
method: 'post',
data: param,
})
}
/**
* 发起聚合支付
*/
export function aggregatePay(param: CheckoutAggregatePayParam) {
return http.request<Result<PayResult>>({
url: '/unipay/checkout/aggregatePay',
method: 'post',
data: param,
})
}
/**
* 收银台认证链接生成参数
*/
export interface CheckoutAuthUrlParam {
/** 要支付的订单号 */
orderNo?: string
/** 聚合支付类型 */
aggregateType?: string
}
/**
* 获取收银台认证结果参数
*/
export interface CheckoutAuthCodeParam {
/** 要支付的订单号 */
orderNo?: string
/** 聚合支付类型 */
aggregateType?: string
/** 认证Code */
authCode?: string
}
/**
* 收银台支付参数
*/
export interface CheckoutPayParam {
/** 订单号 */
orderNo?: string
/** 支付配置项ID */
itemId?: string
/** 唯一标识 */
openId?: string
/** 付款码 */
barCode?: string
}
/**
* 聚合支付参数
*/
export interface CheckoutAggregatePayParam {
/** 订单号 */
orderNo?: string
/** 聚合支付类型 */
aggregateType?: string
/** 唯一标识 */
openId?: string
}
/**
* 收银台配置
*/
export interface CheckoutOrderAndConfigResult {
/** 订单信息 */
order: CheckoutOrderResult
/** 收银台配置信息 */
config: CheckoutConfigResult
/** 收银台分类配置信息 */
groupConfigs: CheckoutGroupConfigResult[]
}
/**
* 收银台聚合支付配置
*/
export interface AggregateOrderAndConfigResult {
/** 订单信息 */
order: CheckoutOrderResult
/** 收银台配置信息 */
config: CheckoutConfigResult
/** 收银台聚合配置信息 */
aggregateConfig: AggregateConfigResult
}
/**
* 订单信息
*/
export interface CheckoutOrderResult {
/** 商户订单号 */
bizOrderNo?: string
/** 订单号 */
orderNo?: string
/** 标题 */
title?: string
/** 描述 */
description?: string
/** 金额(元) */
amount?: number
}
/**
* 收银台配置信息
*/
export interface CheckoutConfigResult {
/** 收银台名称 */
name?: string
/** PC收银台是否同时显示聚合收银码 */
aggregateShow?: boolean
/** h5收银台自动升级聚合支付 */
h5AutoUpgrade?: boolean
}
/**
* 收银台分类配置
*/
export interface CheckoutGroupConfigResult {
/** 主键 */
id?: string
/** 名称 */
name?: string
/** 配置项列表 */
items?: CheckoutItemConfigResult[]
}
/**
* 收银台配置项
*/
export interface CheckoutItemConfigResult {
/** 主键 */
id?: string
/** 发起调用的类型 */
callType?: string
/** 名称 */
name?: string
/** 支付通道 */
channel?: string
/** 支付方式 */
payMethod?: string
}
/**
* 收银台聚合配置信息
*/
export interface AggregateConfigResult {
/** 支付类型 */
type?: string
/** 通道 */
channel?: string
/** 支付方式 */
payMethod?: string
/** 自动拉起支付 */
autoLaunch?: boolean
}

View File

@@ -0,0 +1,118 @@
<template>
<div v-if="show">
<van-overlay :show="loading">
<div class="loading-wrapper" @click.stop>
<van-loading size="24px">
支付中...
</van-loading>
</div>
</van-overlay>
<van-cell-group inset title="订单信息">
<van-cell title="金额" title-style="font-size: 22px;color: red">
<template #default>
<span style="font-size: 22px;color: red">{{ orderAndConfig.order.amount }}</span>
</template>
</van-cell>
<van-field label="标题" :model-value="orderAndConfig.order.title" readonly />
<van-field label="订单号" :model-value="orderAndConfig.order.bizOrderNo" readonly />
<van-field label="支付号" :model-value="orderAndConfig.order.orderNo" readonly />
<van-field label="描述" rows="2" type="textarea" :model-value="orderAndConfig.order.description" readonly />
</van-cell-group>
<van-cell-group v-for="group in orderAndConfig.groupConfigs" :key="group.id" inset :title="group.name">
<van-space direction="vertical" fill>
<van-button v-for="config in group.items" :key="config.id" type="primary" block @click="pay(config)">
{{ config.name }}
</van-button>
</van-space>
</van-cell-group>
</div>
</template>
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { onMounted, ref } from 'vue'
import type {
CheckoutItemConfigResult,
CheckoutOrderAndConfigResult,
CheckoutPayParam,
} from './CheckoutPay.api'
import {
checkoutPay
, getOrderAndConfig,
} from './CheckoutPay.api'
import { CheckoutTypeEnum } from '@/enums/daxpay/DaxPayEnum'
import router from '@/router'
const route = useRoute()
const { orderNo } = route.params
const ua = navigator.userAgent
const show = ref(false)
const loading = ref(false)
const orderAndConfig = ref<CheckoutOrderAndConfigResult>({
order: {},
config: {},
groupConfigs: [],
})
/**
* 初始化
*/
onMounted(() => {
initData()
})
/**
* 初始化数据
*/
async function initData() {
// 获取收银台配置
await getOrderAndConfig(orderNo, CheckoutTypeEnum.H5).then(({ data }) => {
orderAndConfig.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true })
})
// 判断是否自动升级为聚合控制台
if (orderAndConfig.value.config.h5AutoUpgrade) {
goAggregate()
return
}
show.value = true
}
/**
* 跳转到聚合收银台
*/
function goAggregate() {
if (ua.includes('MicroMessenger')) {
router.push({ path: `/aggregate/wechat/${orderNo}`, replace: true })
}
else if (ua.includes('Alipay')) {
router.push({ path: `/aggregate/alipay/${orderNo}`, replace: true })
}
else {
show.value = true
}
}
/**
* 发起支付
*/
function pay(config: CheckoutItemConfigResult) {
loading.value = true
const from = {
orderNo,
itemId: config.id,
} as CheckoutPayParam
checkoutPay(from).then(({ data }) => {
loading.value = false
// 跳转到支付页面
location.replace(data.payBody)
})
}
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,89 @@
<template>
<div>
<van-overlay :show="loading">
<div class="loading-wrapper" @click.stop>
<van-loading size="24px">
支付中...
</van-loading>
</div>
</van-overlay>
<van-cell-group inset title="订单信息">
<van-cell title="金额" title-style="font-size: 22px;color: red">
<template #default>
<span style="font-size: 22px;color: red">{{ aggregateInfo.order.amount }}</span>
</template>
</van-cell>
<van-field label="标题" :model-value="aggregateInfo.order.title" readonly />
<van-field label="订单号" :model-value="aggregateInfo.order.bizOrderNo" readonly />
<van-field label="支付号" :model-value="aggregateInfo.order.orderNo" readonly />
<van-field label="描述" rows="2" type="textarea" :model-value="aggregateInfo.order.description" readonly />
</van-cell-group>
<van-submit-bar safe-area-inset-bottom :price="(aggregateInfo.order.amount || 0)*100" button-text="支付" @submit="pay" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { CheckoutAggregateEnum } from '@/enums/daxpay/DaxPayEnum'
import router from '@/router'
import type {
AggregateOrderAndConfigResult,
CheckoutAggregatePayParam,
} from '@/views/daxpay/checkout/CheckoutPay.api'
import {
aggregatePay,
getAggregateConfig,
} from '@/views/daxpay/checkout/CheckoutPay.api'
const route = useRoute()
const { orderNo } = route.params
const loading = ref<boolean>(false)
const aggregateInfo = ref<AggregateOrderAndConfigResult>({
aggregateConfig: {},
config: {},
order: {},
})
onMounted(() => {
initData()
})
/**
* 初始化数据
*/
async function initData() {
// 查询订单和配置
await getAggregateConfig(orderNo, CheckoutAggregateEnum.ALI).then(({ data }) => {
aggregateInfo.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true })
})
// 判断是否自动拉起支付
if (aggregateInfo.value.aggregateConfig.autoLaunch) {
pay()
}
}
/**
* 支付
*/
function pay() {
loading.value = true
const from = {
orderNo: aggregateInfo.value.order.orderNo,
aggregateType: CheckoutAggregateEnum.ALI,
} as CheckoutAggregatePayParam
aggregatePay(from)
.then(({ data }) => {
loading.value = false
// 跳转到H5/扫码支付页面
location.replace(data.payBody)
})
}
</script>
<style scoped lang="less">
</style>

View File

@@ -0,0 +1,152 @@
<template>
<div v-if="show">
<van-overlay :show="loading">
<div class="loading-wrapper" @click.stop>
<van-loading size="24px">
支付中...
</van-loading>
</div>
</van-overlay>
<van-cell-group inset>
<van-cell title="金额" title-style="font-size: 22px;color: red">
<template #default>
<span style="font-size: 22px;color: red">{{ aggregateInfo.order.amount }}</span>
</template>
</van-cell>
<van-field label="标题" :model-value="aggregateInfo.order.title" readonly />
<van-field label="订单号" :model-value="aggregateInfo.order.bizOrderNo" readonly />
<van-field label="支付号" :model-value="aggregateInfo.order.orderNo" readonly />
<van-field label="描述" rows="2" type="textarea" :model-value="aggregateInfo.order.description" readonly />
</van-cell-group>
<van-submit-bar safe-area-inset-bottom :price="(aggregateInfo.order.amount || 0) * 100" button-text="支付" @submit="pay" />
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import { CheckoutAggregateEnum } from '@/enums/daxpay/DaxPayEnum'
import router from '@/router'
import type {
AggregateOrderAndConfigResult, CheckoutAggregatePayParam,
CheckoutAuthCodeParam} from '@/views/daxpay/checkout/CheckoutPay.api';
import { aggregatePay,
auth, generateAuthUrl, getAggregateConfig } from '@/views/daxpay/checkout/CheckoutPay.api'
import type { WxJsapiSignResult } from '@/views/daxpay/aggregate/Aggregate.api'
const route = useRoute()
const { orderNo } = route.params
const { code: authCode } = route.query
const show = ref<boolean>(false)
const loading = ref<boolean>(false)
const openId = ref<string>('')
// 认证参数
const authParam = ref<CheckoutAuthCodeParam>({
orderNo: orderNo as string,
aggregateType: CheckoutAggregateEnum.WECHAT,
})
const aggregateInfo = ref<AggregateOrderAndConfigResult>({
aggregateConfig: {},
config: {},
order: {},
})
onMounted(() => {
init()
})
/**
* 进入页面的初始化
*/
function init() {
// 如果不是重定向跳转过来, 跳转到到重定向授权地址
if (!authCode) {
// 重定向跳转到微信授权地址
generateAuthUrl({ orderNo: orderNo as string, aggregateType: CheckoutAggregateEnum.WECHAT }).then((res) => {
const url = res.data
location.replace(url)
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true })
})
}
else {
authParam.value.authCode = authCode as string
// 初始化数据
initData()
}
}
/**
* 初始化数据
*/
async function initData() {
show.value = true
// 获取聚合配置
getAggregateConfig(orderNo, CheckoutAggregateEnum.WECHAT).then(({ data }) => {
aggregateInfo.value = data
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true })
})
// 认证获取OpenId
await auth(authParam.value).then(({ data }) => {
openId.value = data.openId as string
}).catch((res) => {
router.push({ name: 'ErrorResult', query: { msg: res.message }, replace: true })
})
// 判断是否自动拉起支付
if (aggregateInfo.value.aggregateConfig.autoLaunch) {
pay()
}
}
/**
* 微信jsapi方式支付
*/
function pay() {
loading.value = true
const from = {
orderNo: orderNo as string,
aggregateType: CheckoutAggregateEnum.WECHAT,
openId: openId.value,
} as CheckoutAggregatePayParam
aggregatePay(from)
.then(({ data }) => {
loading.value = false
// 拉起jsapi支付
const json = JSON.parse(data.payBody)
jsapiPay(json)
})
}
/**
* 拉起Jsapi支付窗口
*/
function jsapiPay(data: WxJsapiSignResult) {
const form = {
appId: data.appId, // 公众号ID由商户传入
timeStamp: data.timeStamp, // 时间戳自1970年以来的秒数
nonceStr: data.nonceStr, // 随机串
package: data.package, // 预支付ID
signType: data.signType, // 微信签名方式:
paySign: data.paySign, // 微信签名
}
// 使用微信JsSdk拉起支付
WeixinJSBridge.invoke('getBrandWCPayRequest', form, (res) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 跳转到成功页面
router.push({ name: 'SuccessResult', query: { msg: '支付成功' }, replace: true })
}
})
}
</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,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped lang="less">
</style>

View File

@@ -1,63 +0,0 @@
import { http } from '@/utils/http/axios'
import type { Result } from '#/axios'
/**
* 获取支付信息
*/
export function getInfo(code): Promise<Result<AggregatePayInfo>> {
return http.request({
url: '/demo/aggregate/getInfo',
method: 'get',
params: { code },
})
}
/**
* 支付宝h5支付
*/
export function aliH5Pay(code): Promise<Result<PayOrderResult>> {
return http.request({
url: '/demo/aggregate/aliH5Pay',
method: 'post',
params: { code },
})
}
/**
* 获取微信H5预支付签名信息
*/
export function getWxJsapiPay(aggregateCode, openId): Promise<Result<WxJsapiSignResult>> {
return http.request({
url: '/demo/aggregate/getWxJsapiPay',
method: 'post',
params: { aggregateCode, openId },
})
}
/**
*支付信息
*/
export interface AggregatePayInfo {
title?: string
bizOrderNo?: string
amount: number
}
/**
* 发起支付后响应对象
*/
export interface PayOrderResult {
payBody: string
}
/**
* 微信Jsapi预支付签名返回信息
*/
export interface WxJsapiSignResult {
appId?: string
timeStamp?: string
nonceStr?: string
signType?: string
paySign?: string
package?: string
}

View File

@@ -1,95 +0,0 @@
<template>
<div class="page-container flex flex-col justify-center">
<van-overlay lock-scroll :show="loading">
<Loading text="调起支付中..." />
</van-overlay>
<div class="text-center">
<h1>金额: {{ info.amount / 100.0 }} </h1>
<h1>标题: {{ info.title }}</h1>
<h1>业务号: {{ info.businessNo }}</h1>
</div>
<div class="text-center">
<van-button type="primary" @click="pay">
去支付
</van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import { useRouter } from 'vue-router'
import type { AggregatePayInfo } from './Aggregate.api'
import { aliH5Pay, getInfo } from './Aggregate.api'
import router from '@/router'
import Loading from '@/components/Loading.vue'
onMounted(() => {
init()
})
const { currentRoute } = useRouter()
const { query } = unref(currentRoute)
const loading = ref<boolean>(false)
const code = ref<string>(query.code as string)
const info = ref<AggregatePayInfo>({ amount: 0 })
/**
* 根据code获取支付信息
*/
async function init() {
loading.value = true
await getInfo(code.value)
.then(({ data }) => {
info.value = data
loading.value = false
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: err.message },
})
})
// 调起支付
pay()
}
/**
* 发起支付
*/
function pay() {
loading.value = true
aliH5Pay(code.value)
.then(({ data }) => {
loading.value = false
// 跳转到H5支付页面
window.location.href = data.payBody
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: err.message },
})
})
}
</script>
<style lang="less" scoped>
.page-container {
width: 100%;
border-radius: 4px;
padding: 50px 0;
height: 100vh;
.text-center {
h1 {
color: #666;
padding: 2vh 0;
font-size: x-large;
}
}
}
</style>

View File

@@ -1,118 +0,0 @@
<template>
<div class="page-container flex flex-col justify-center">
<van-overlay lock-scroll :show="loading">
<Loading text="调起支付中..." />
</van-overlay>
<div class="text-center">
<h1>金额: {{ info.amount / 100.0 }} </h1>
<h1>标题: {{ info.title }}</h1>
<h1>订单号: {{ info.bizOrderNo }}</h1>
</div>
<div class="text-center">
<van-button type="primary" @click="pay">
去支付
</van-button>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import { useRouter } from 'vue-router'
import type { AggregatePayInfo, WxJsapiSignResult } from './Aggregate.api'
import { getInfo, getWxJsapiPay } from './Aggregate.api'
import router from '@/router'
import Loading from '@/components/Loading.vue'
const { currentRoute } = useRouter()
const { query } = unref(currentRoute)
const aggregateCode = ref<string>(query.aggregateCode as string)
const openId = ref<string>(query.openId as string)
const info = ref<AggregatePayInfo>({ amount: 0 })
const loading = ref<boolean>(false)
onMounted(() => {
init()
})
/**
* 根据code获取支付信息
*/
async function init() {
loading.value = true
// 获取支付信息
await getInfo(aggregateCode.value)
.then(({ data }) => {
loading.value = false
info.value = data
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: err.message },
})
})
// 调起支付
pay()
}
/**
* 发起支付
*/
function pay() {
loading.value = true
getWxJsapiPay(aggregateCode.value, openId.value)
.then(({ data }) => {
loading.value = false
doPay(data)
})
.catch(() => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: '支付失败' },
})
})
}
/**
* 拉起支付
*/
function doPay(data: WxJsapiSignResult) {
const form = {
appId: data.appId, // 公众号ID由商户传入
timeStamp: data.timeStamp, // 时间戳自1970年以来的秒数
nonceStr: data.nonceStr, // 随机串
package: data.package, // 预支付ID
signType: data.signType, // 微信签名方式:
paySign: data.paySign, // 微信签名
}
// 使用微信JsSdk拉起支付
WeixinJSBridge.invoke('getBrandWCPayRequest', form, (res) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
setTimeout(() => {
WeixinJSBridge.call('closeWindow')
}, 0)
}
})
}
</script>
<style lang="less" scoped>
.page-container {
width: 100%;
border-radius: 4px;
padding: 50px 0;
height: 100vh;
.text-center {
h1 {
color: #666;
padding: 2vh 0;
font-size: x-large;
}
}
}
</style>

View File

@@ -1,41 +0,0 @@
import { http } from '@/utils/http/axios'
import type { Result } from '#/axios'
/**
* 获取当前可用支付的环境
*/
export function getPayEnv(): Promise<Result<string>> {
return http.request({
url: '/demo/cashier/getPayEnv',
method: 'get',
})
}
/**
* 获取微信授权请求页URL
*/
export function getWxAuthUrl(): Promise<Result<string>> {
return http.request({
url: '/demo/cashier/getWxAuthUrl',
method: 'get',
})
}
/**
* 单独支付
*/
export function simplePayCashier(obj): Promise<Result<PayOrderResult>> {
return http.request({
url: '/demo/cashier/simplePayCashier',
method: 'post',
data: obj,
})
}
/**
* 发起支付后响应对象
*/
export interface PayOrderResult {
// 支付参数体(通常用于发起异步支付的参数)
payBody: string
}

View File

@@ -1,295 +0,0 @@
<template>
<div v-show="pageShow">
<div class="h-screen flex justify-center pt-8">
<van-overlay lock-scroll :show="loading">
<Loading text="调起支付中..." />
</van-overlay>
<div class="w-full flex flex-col">
<div class="flex flex-col items-center justify-center">
<div class="logo enter-y my-35px">
<SvgIcon class="!h-150px !w-150px" name="logo" />
</div>
<div class="text-darkBlue dark:text-garyWhite enter-y mb-80px text-45px font-black">
手机收银台
</div>
<van-form ref="formRef">
<van-cell-group inset>
<van-field
v-model="amount"
type="number"
required
label="金额"
:rules="[
{ required: true, message: '请输入要支付的金额' },
{ pattern: /^\d+(\.\d{2})?$/, message: '金额最为多两位小数' },
{
validator: validatorAmount,
message: '请输入0.01-100之间的金额',
},
]"
/>
<van-field name="switch" label="过滤支付方式">
<template #input>
<van-switch v-model="filterChannelFlag" size="24px" @change="initChannelList" />
</template>
</van-field>
</van-cell-group>
</van-form>
<van-action-sheet
v-model:show="channelShow"
safe-area-inset-bottom
:actions="channelList"
@select="pay"
/>
<van-action-bar safe-area-inset-bottom>
<van-action-bar-button type="danger" text="支付" @click="paySelect" />
</van-action-bar>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref, unref } from 'vue'
import type { ActionSheetAction } from 'vant'
import { useRouter } from 'vue-router'
import { getPayEnv, getWxAuthUrl, simplePayCashier } from '@/views/demo/cashier/Cashier.api'
import router from '@/router'
import { payChannelEnum } from '@/enums/payment/payChannelEnum'
import { payMethodEnum } from '@/enums/payment/payMethodEnum'
import type { WxJsapiSignResult } from '@/views/demo/aggregate/Aggregate.api'
import Loading from '@/components/Loading.vue'
const pageShow = ref<boolean>(false)
const loading = ref<boolean>(false)
const channelShow = ref<boolean>(false)
const filterChannelFlag = ref<boolean>(true)
const amount = ref<number>(0.01)
const formRef = ref<any>()
// 当前环境
const currentEnv = ref<string>('all')
const openId = ref<string>('')
const channelList = ref<ActionSheetAction[]>([])
const { currentRoute } = useRouter()
const { query } = unref(currentRoute)
onMounted(() => {
init()
})
/**
* 初始化
*/
async function init() {
await getPayEnv().then(({ data }) => {
currentEnv.value = data
})
// 是否在微信环境中. 在的话获取微信授权
if (currentEnv.value === payChannelEnum.WECHAT) {
const source = query.source as string
// 重定向则获取OpenId
if (source === 'redirect') {
openId.value = query.openId as string
}
else {
// 跳转到微信授权地址
const { data: url } = await getWxAuthUrl()
location.replace(url)
return
}
}
initChannelList()
pageShow.value = true
}
/**
* 初始化通道列表
*/
function initChannelList() {
channelList.value = [
{
name: '支付宝',
disabled: channelFilter(payChannelEnum.ALI),
},
{
name: '微信',
disabled: channelFilter(payChannelEnum.WECHAT),
},
]
}
/**
* 过滤当前的状态是否可以使用
*/
function channelFilter(channel: string): boolean {
// 未开启过滤
if (!filterChannelFlag.value) {
return false
}
// 返回为all
if (currentEnv.value === 'all') {
return false
}
// 如果返回通道, 只有当前符合环境的可以用
return currentEnv.value !== channel
}
/**
* 校验支付金额
*/
function validatorAmount(input: string) {
if (Number.parseFloat(input) >= 0.01 && Number.parseFloat(input) <= 100) {
return Promise.resolve(true)
}
else {
return Promise.resolve(false)
}
}
/**
* 调起支付选择
*/
function paySelect() {
// 校验表单
formRef.value.validate().then(() => {
// 拉起选择弹窗
channelShow.value = true
})
}
/**
* 发起支付
*/
function pay(action: ActionSheetAction) {
if (action.name === '微信') {
wechatPay()
}
else {
aliPay()
}
}
/**
* 发起微信支付
* 1. 微信中使用jsapi
* 2. 外部浏览器使用wap支付
*/
function wechatPay() {
if (currentEnv.value === 'wechat_pay') {
wechatJsapiPay()
}
else {
wechatH5Pay()
}
}
/**
* 微信jsapi方式支付
*/
function wechatJsapiPay() {
loading.value = true
const from = {
bizOrderNo: `M${new Date().getTime()}`,
title: '测试H5支付',
amount: amount.value,
channel: payChannelEnum.WECHAT,
method: payMethodEnum.JSAPI,
openId: openId.value,
}
simplePayCashier(from)
.then(({ data }) => {
loading.value = false
// 拉起jsapi支付
const json = JSON.parse(data.payBody)
doJsapiPay(json)
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: err.message },
})
})
}
/**
* 拉起Jsapi支付窗口
*/
function doJsapiPay(data: WxJsapiSignResult) {
const form = {
appId: data.appId, // 公众号ID由商户传入
timeStamp: data.timeStamp, // 时间戳自1970年以来的秒数
nonceStr: data.nonceStr, // 随机串
package: data.package, // 预支付ID
signType: data.signType, // 微信签名方式:
paySign: data.paySign, // 微信签名
}
// 使用微信JsSdk拉起支付
WeixinJSBridge.invoke('getBrandWCPayRequest', form, (res) => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
setTimeout(() => {
WeixinJSBridge.call('closeWindow')
}, 0)
}
})
}
/**
* 微信h5支付
*/
function wechatH5Pay() {
loading.value = true
const from = {
bizOrderNo: new Date().getTime(),
title: '测试H5支付',
amount: amount.value,
channel: payChannelEnum.WECHAT,
method: payMethodEnum.WAP,
}
simplePayCashier(from)
.then(({ data }) => {
loading.value = false
// 跳转到H5支付页面
location.replace(data.payBody)
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: err.message },
})
})
}
/**
* 支付宝支付, 使用h5支付
*/
function aliPay() {
loading.value = true
const from = {
bizOrderNo: new Date().getTime(),
title: '测试H5支付',
amount: amount.value,
channel: payChannelEnum.ALI,
method: payMethodEnum.WAP,
}
simplePayCashier(from)
.then(({ data }) => {
loading.value = false
// 跳转到H5支付页面
window.location.href = data.payBody
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'PayErrorResult',
query: { msg: err.message },
})
})
}
</script>
<style scoped lang="less"></style>

View File

@@ -1,25 +0,0 @@
import { http } from '@/utils/http/axios'
import type { Result } from '#/axios'
/**
* 通过key获取信息路由信息
*/
export function getRouterByKey(key: string): Promise<Result<RouterInfo>> {
return http.request({
url: '/short/link/getRouterByKey',
method: 'get',
params: { key },
})
}
/**
* 路由信息
*/
interface RouterInfo {
// key值
key: string
// 路由键
route: string
// 数据信息
data: string
}

View File

@@ -1,27 +0,0 @@
<template>
<div> 跳转中... </div>
</template>
<script setup lang="ts">
// 获取路由参数
import { useRouter } from 'vue-router'
import { onMounted } from 'vue'
import { getRouterByKey } from './Dispatch.api'
import router from '@/router'
const { currentRoute } = useRouter()
const { params } = unref(currentRoute)
onMounted(() => {
const key = params.key as string
getRouterByKey(key).then(({ data }) => {
console.log(data)
router.push({
name: data.route,
query: { data: data.data },
})
})
})
</script>
<style scoped></style>

View File

@@ -303,6 +303,6 @@ declare global {
// for type re-export
declare global {
// @ts-ignore
export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue'
export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'
import('vue')
}