feat 收银台开发

This commit is contained in:
DaxPay
2024-09-29 18:53:27 +08:00
parent 52022dee5a
commit 9adc2a760a
9 changed files with 380 additions and 56 deletions

4
components.d.ts vendored
View File

@@ -10,8 +10,10 @@ declare module 'vue' {
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']
}
}

View File

@@ -0,0 +1,28 @@
/**
* 支付通道
*/
export enum ChannelEnum {
ALI = 'ali_pay',
WECHAT = 'wechat_pay',
UNION_PAY = 'union_pay',
}
/**
* 支付方式
*/
export enum payMethodEnum {
WAP = 'wap',
APP = 'app',
WEB = 'web',
QRCODE = 'qrcode',
BARCODE = 'barcode',
JSAPI = 'jsapi',
}
/**
* 收银台类型
*/
export enum CashierTypeEnum {
WECHAT_PAY = 'wechat_pay',
ALIPAY = 'alipay',
}

View File

@@ -1,13 +0,0 @@
/**
* 支付通道
*/
export enum payChannelEnum {
ALI = 'ali_pay',
WECHAT = 'wechat_pay',
UNION_PAY = 'union_pay',
CASH = 'cash_pay',
WALLET = 'wallet_pay',
VOUCHER = 'voucher_pay',
CREDIT_CARD = 'credit_pay',
AGGREGATION = 'aggregation_pay',
}

View File

@@ -1,12 +0,0 @@
/**
* 支付通道
*/
export enum payMethodEnum {
NORMAL = 'normal',
WAP = 'wap',
APP = 'app',
WEB = 'web',
QRCODE = 'qrcode',
BARCODE = 'barcode',
JSAPI = 'jsapi',
}

View File

@@ -25,7 +25,7 @@ export const BusinessRoute: RouteRecordRaw = {
name: 'SuccessResult',
component: () => import('@/views/result/SuccessResult.vue'),
meta: {
title: '支付成功',
title: '操作成功',
},
},
{

View File

@@ -1,14 +1,54 @@
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',
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 },
})
}
/**
* 获取收银台所需授权链接, 用于获取OpenId一类的信息
*/
export function generateAuthUrl(param: CashierAuthCodeParam) {
return http.request<Result<string>>({
url: '/unipay/ext/channel/cashier/authCode',
method: 'POST',
data: param,
})
}
/**
* 发起支付
*/
export function cashierPay(param: CashierPayParam) {
return http.request<Result<PayResult>>({
url: '/unipay/ext/channel/cashier/pay',
method: 'POST',
data: param,
})
}
/**
* 通道认证参数
*/
export interface CashierAuthCodeParam {
// 标识码
authCode?: string
// 查询Code
queryCode?: string
// 商户号
mchNo?: string
// 应用号
@@ -16,3 +56,77 @@ export interface CashierAuthCodeParam {
// 收银台类型
cashierType?: string
}
/**
* 通道收银支付参数
*/
export interface CashierPayParam {
// 商户号
mchNo?: string
// 应用号
appId?: string
// 收银台类型
cashierType?: string
// 支付金额
amount?: number
// 标识码
authCode?: string
// 支付描述
description?: string
}
/**
* 支付结果
*/
export interface PayResult {
// 支付状态
status: string
// 支付参数体
payBody: string
// 商户订单号
bizOrderNo: string
// 订单号
orderNo: string
}
/**
* 微信Jsapi预支付签名返回信息
*/
export interface WxJsapiSignResult {
// 公众号ID由商户传入
appId?: string
// 时间戳自1970年以来的秒数
timeStamp?: string
// 随机串
nonceStr?: string
// 预支付ID
package?: string
// 微信签名方式:
signType?: string
// 微信签名
paySign?: string
}
/**
* 收银台配置信息
*/
export interface ChannelCashierConfigResult {
// 商户号
mchNo?: string
// 应用号
appId?: string
// 收银台类型
cashierType?: string
// 收银台名称
cashierName?: string
// 支付通道
channel?: string
// 支付方式
payMethod?: string
// 是否开启分账
allocation?: boolean
// 自动分账
autoAllocation?: boolean
}

View File

@@ -13,8 +13,8 @@
<script setup lang="ts">
import { useRoute } from 'vue-router'
import { ref } from 'vue'
import router from '@/router'
import { ref } from "vue";
const route = useRoute()

View File

@@ -1,9 +1,201 @@
<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="show"
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="show = true">
添加备注
</div>
</div>
</template>
</van-number-keyboard>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
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'
const route = useRoute()
const { mchNo, appId } = route.params
const show = 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>('')
onMounted(() => {
initData()
})
/**
* 初始化数据
*/
function initData() {
getCashierInfo(CashierTypeEnum.ALIPAY, appId as string).then(({ data }) => {
cashierInfo.value = data
})
getMchName(mchNo as string).then(({ data }) => {
mchName.value = data
})
}
/**
* 输入
*/
function input(value: string) {
const amountStr = amount.value
if (amountStr === '0') {
if (value === '.') {
amount.value = '0.'
return
}
amount.value = String(value)
}
else {
// 只允许有一个小数点
if (value === '.' && amountStr.includes('.')) {
return
}
// 小数最多两位
if (amountStr.includes('.') && amountStr.length - amountStr.indexOf('.') > 2) {
return
}
// 金额最高100万
if (amountStr.split('.')[0].length > 7 && value !== '.') {
return
}
amount.value = amountStr.concat(value)
}
}
/**
* 删除
*/
function del() {
if (amount.value.length > 1) {
amount.value = amount.value.substring(0, amount.value.length - 1)
}
else {
amount.value = '0'
}
}
/**
* 支付
*/
function pay() {
// 金额不可为0
if (amount.value === '0') {
return
}
loading.value = true
const from = {
amount: Number(amount.value),
appId,
cashierType: CashierTypeEnum.ALIPAY,
description: description.value,
mchNo,
} as CashierPayParam
cashierPay(from)
.then(({ data }) => {
loading.value = false
// 跳转到H5/付款码支付页面
location.replace(data.payBody)
})
.catch((err) => {
// 跳转到错误页
router.push({
name: 'ErrorResult',
query: { msg: err.message },
})
})
}
</script>
<style scoped lang="less">
<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,11 +1,30 @@
<template>
<div>
<div class="container">
<h2>收银台</h2>
<div class="amount-display">
<p>付款给骏易</p>
<p class="amount">¥ 0</p>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import type { WxJsapiSignResult } from '@/views/demo/aggregate/Aggregate.api'
import type { CashierAuthCodeParam } from '@/views/daxpay/channel/ChannelCashier.api'
import type {
CashierAuthCodeParam,
CashierPayParam,
WxJsapiSignResult,
} from '@/views/daxpay/channel/ChannelCashier.api'
import {
cashierPay,
generateAuthUrl,
} from '@/views/daxpay/channel/ChannelCashier.api'
import { CashierTypeEnum } from '@/enums/daxpay/DaxPayEnum'
import router from '@/router'
const route = useRoute()
const { mchNo, appId } = route.params
@@ -18,8 +37,7 @@ const amount = ref<number>(0.0)
const authParam = ref<CashierAuthCodeParam>({
mchNo: mchNo as string,
appId: appId as string,
authCode: code as string,
cashierType: 'wechat_pay',
cashierType: CashierTypeEnum.WECHAT_PAY,
})
onMounted(() => {
@@ -32,17 +50,15 @@ onMounted(() => {
function init() {
// 如果不是重定向跳转过来, 跳转到到重定向地址
if (!code) {
// 跳转到微信授权地址
const url = ''
location.replace(url)
// 重定向跳转到微信授权地址
generateAuthUrl(authParam.value).then((res) => {
const url = res.data
location.replace(url)
})
}
else {
// 获取并设置OpenId
// 发起收银台支付
// 获取jsapi调用方式
doJsapiPay()
wechatPay()
}
}
@@ -52,14 +68,13 @@ function init() {
function wechatPay() {
loading.value = true
const from = {
bizOrderNo: `M${new Date().getTime()}`,
title: '测试H5支付',
amount: amount.value,
channel: ChannelEnum.WECHAT,
method: MethodEnum.JSAPI,
openId: openId.value,
}
simplePayCashier(from)
appId,
authCode: code,
cashierType: CashierTypeEnum.WECHAT_PAY,
mchNo,
} as CashierPayParam
cashierPay(from)
.then(({ data }) => {
loading.value = false
// 拉起jsapi支付
@@ -91,9 +106,7 @@ function doJsapiPay(data: WxJsapiSignResult) {
WeixinJSBridge.invoke('getBrandWCPayRequest', form, () => {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
// 跳转到成功页面
setTimeout(() => {
WeixinJSBridge.call('closeWindow')
}, 0)
router.push({ name: 'SuccessResult', query: { msg: '支付成功' }, replace: true })
}
})
}