|
|
@@ -1,5 +1,6 @@
|
|
|
<template>
|
|
|
<view class="app-collect_money">
|
|
|
+ <view class="close-icon" @click="closePayPanel">×</view>
|
|
|
<view v-if="showSuccess == false">
|
|
|
<view class="collect-code_box">
|
|
|
<image class="img" :src="`${constant.imgUrl}/icon/scan/scan.png`" mode="scaleToFill" @click="beginScan()" />
|
|
|
@@ -15,12 +16,26 @@
|
|
|
</view>
|
|
|
<view style="margin-top:10upx;font-weight:bold;font-size:34upx;color:black;">¥{{price}}</view>
|
|
|
|
|
|
- <view class="show-code" v-if="goPay == 0">请出示付款码</view>
|
|
|
+ <view class="show-code" v-if="goPay == 0 && !showManualInput" @click="openManualInput">请出示付款码</view>
|
|
|
+ <view class="manual-code-box" v-if="goPay == 0 && showManualInput">
|
|
|
+ <input
|
|
|
+ class="manual-code-input"
|
|
|
+ v-model="manualAuthCode"
|
|
|
+ type="number"
|
|
|
+ maxlength="32"
|
|
|
+ placeholder="请出示付款码"
|
|
|
+ @confirm="submitManualCode"
|
|
|
+ />
|
|
|
+ <view class="manual-code-actions">
|
|
|
+ <view class="manual-btn" @click="closeManualInput">返回</view>
|
|
|
+ <view class="manual-btn manual-btn-primary" @click="submitManualCode">确认支付</view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
<view class="paying" v-if="goPay == 1">请求中</view>
|
|
|
<view style="font-size:18upx;margin:8upx auto;color:red;font-weight:bold;text-align:center;" v-if="goPay == 2">
|
|
|
<view style="font-size:16upx;line-height:1.3;max-width:90%;margin:0 auto;">{{ remindText }}</view>
|
|
|
<view style="margin-top:6upx;display:flex;justify-content:center;" v-if="returnCode=='BBS11105' || returnCode=='BBS10000'">
|
|
|
- <text style="color:green;border:1upx solid green;border-radius:8upx;padding:4upx 12upx;font-size:18upx;font-weight:bold;line-height:1.2;background-color:transparent;" @click="checkOrder">
|
|
|
+ <text style="color:white;border:1upx solid red;border-radius:8upx;padding:4upx 12upx;font-size:18upx;font-weight:bold;line-height:1.2;background-color:red;" @click="checkOrder">
|
|
|
查询结果
|
|
|
</text>
|
|
|
</view>
|
|
|
@@ -28,8 +43,11 @@
|
|
|
<view v-if="goPay == 1">
|
|
|
<image style="width:25upx;height:25upx;margin:0 auto;" :src="`${constant.imgUrl}/icon/loading.gif`" mode="scaleToFill" />
|
|
|
</view>
|
|
|
- <view style="margin-top:10upx;">
|
|
|
- <view @click="cancelPayFn" class="btn-default">取消</view>
|
|
|
+ <view style="margin-top:10upx;display:flex;justify-content:center;">
|
|
|
+ <view style="position:relative;">
|
|
|
+ <view @click="cancelPayFn(0)" class="btn-default">取消</view>
|
|
|
+ <view @click="cancelPayFn(1)" style="position:absolute;left:100%;bottom:0;margin-left:10upx;font-size:13upx;color:black;white-space:nowrap;">强制取消</view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -51,12 +69,22 @@
|
|
|
</view>
|
|
|
</uni-popup>
|
|
|
|
|
|
+ <uni-popup ref="closePaymentRef" type="dialog">
|
|
|
+ <view style="width:60vw;height:70vh;background-color: white;padding:70upx 20upx 0 20upx;">
|
|
|
+ <view style="margin-bottom:40upx;text-align: center;font-size:24upx;">确认关闭?</view>
|
|
|
+ <view style="text-align:center;">
|
|
|
+ <button class="admin-button-com mini-btn default" @click="returnClose">返回</button>
|
|
|
+ <button class="admin-button-com mini-btn default" @click="confirmClose" style="margin-left:30upx;">确认</button>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </uni-popup>
|
|
|
+
|
|
|
</view>
|
|
|
</template>
|
|
|
<script>
|
|
|
/**
|
|
|
* 扫码支付组件(供货商收银机端)
|
|
|
- * 解决客户扫码后转圈状态被卡住、钱未到账的问题,支持网络异常降级和自动后台复查
|
|
|
+ * 支持手动输入与扫码填入付款码后确认提交,解决客户扫码后转圈状态被卡住、钱未到账的问题,支持网络异常降级和自动后台复查
|
|
|
*/
|
|
|
import { codePay,codePayCheck,codePayVerify,cancelOrderFn,getDetail} from "@/api/order";
|
|
|
export default {
|
|
|
@@ -72,9 +100,14 @@ export default {
|
|
|
returnCode:'',
|
|
|
cancelText:'确认要取消?',
|
|
|
isCancelDialogOpen:false,
|
|
|
+ isCloseDialogOpen:false, // 关闭确认弹框是否打开
|
|
|
isPaymentProcessing:false, // 标志位:防重复扫码
|
|
|
pollingTimer:null, // 后台轮询复查定时器
|
|
|
- pollingCount:0 // 轮询计数器
|
|
|
+ pollingCount:0, // 轮询计数器
|
|
|
+ showManualInput:false, // 是否展示手动输入付款码
|
|
|
+ manualAuthCode:'', // 手动输入的付款码
|
|
|
+ manualInputFocus:false, // 控制输入框自动聚焦
|
|
|
+ isForceCancel: 0 // 0: 普通取消,1: 强制取消
|
|
|
}
|
|
|
},
|
|
|
props: {
|
|
|
@@ -100,7 +133,7 @@ export default {
|
|
|
//避免重复,每次进来先移除全局自定义事件监听器
|
|
|
uni.$off('listenGetScanCode')
|
|
|
uni.$on('listenGetScanCode',function(data){
|
|
|
- that.requestCodePay(data.code)
|
|
|
+ that.fillAuthCodeFromScan(data.code)
|
|
|
})
|
|
|
},
|
|
|
beforeDestroy(){
|
|
|
@@ -114,7 +147,7 @@ export default {
|
|
|
uni.scanCode({
|
|
|
onlyFromCamera: true,
|
|
|
success: function (res) {
|
|
|
- that.requestCodePay(res.result)
|
|
|
+ that.fillAuthCodeFromScan(res.result)
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
@@ -124,14 +157,74 @@ export default {
|
|
|
this.goPay = 0
|
|
|
this.returnCode = ''
|
|
|
this.isPaymentProcessing = false
|
|
|
+ this.closeManualInput()
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 点击「请出示付款码」后展开手动输入区域
|
|
|
+ */
|
|
|
+ openManualInput(){
|
|
|
+ this.showManualInput = true
|
|
|
+ this.manualAuthCode = ''
|
|
|
+ this.manualInputFocus = false
|
|
|
+ this.$nextTick(() => {
|
|
|
+ this.manualInputFocus = true
|
|
|
+ })
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 关闭付款码输入区域
|
|
|
+ */
|
|
|
+ closeManualInput(){
|
|
|
+ this.showManualInput = false
|
|
|
+ this.manualAuthCode = ''
|
|
|
+ this.manualInputFocus = false
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 处理扫码:未展开输入框时自动提交,已展开时仅填入等待确认
|
|
|
+ * @param {string} rawCode - 扫码原始内容
|
|
|
+ */
|
|
|
+ fillAuthCodeFromScan(rawCode){
|
|
|
+ if(this.isCancelDialogOpen || this.isCloseDialogOpen){
|
|
|
+ this.$msg('请先完成当前操作')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if(this.isPaymentProcessing){
|
|
|
+ this.$msg('支付处理中,请稍候')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const authCode = String(rawCode || '').replace(/\s+/g, '')
|
|
|
+ if(!authCode){
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if(this.showManualInput){
|
|
|
+ this.clearPolling()
|
|
|
+ this.remindText = ''
|
|
|
+ this.returnCode = ''
|
|
|
+ this.goPay = 0
|
|
|
+ this.manualAuthCode = authCode
|
|
|
+ this.manualInputFocus = false
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.requestCodePay(authCode)
|
|
|
+ },
|
|
|
+ /**
|
|
|
+ * 确认提交输入框中的付款码
|
|
|
+ */
|
|
|
+ submitManualCode(){
|
|
|
+ const authCode = String(this.manualAuthCode || '').replace(/\s+/g, '')
|
|
|
+ if (!authCode) {
|
|
|
+ this.$msg('请输入付款码')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.closeManualInput()
|
|
|
+ this.requestCodePay(authCode)
|
|
|
},
|
|
|
checkOrder(){
|
|
|
let that = this
|
|
|
uni.showLoading({mask:true})
|
|
|
- getDetail({id:this.orderId}).then(res=>{
|
|
|
+ codePayVerify({id:this.orderId}).then(res=>{
|
|
|
uni.hideLoading()
|
|
|
if(res.code == 1){
|
|
|
- if(res.data.payStatus == 1){
|
|
|
+ if(res.data && res.data.returnStatus == 'SUCCESS'){
|
|
|
// 手动查询付款成功,清除复查轮询并回调
|
|
|
that.clearPolling()
|
|
|
that.isPaymentProcessing = false
|
|
|
@@ -140,15 +233,20 @@ export default {
|
|
|
that.payFinish()
|
|
|
},900)
|
|
|
}else{
|
|
|
- this.$msg('还没有付款成功')
|
|
|
+ this.$msg(res.msg || '还没有付款成功')
|
|
|
}
|
|
|
+ } else {
|
|
|
+ this.$msg(res.msg || '查询失败')
|
|
|
}
|
|
|
+ }).catch(err => {
|
|
|
+ uni.hideLoading()
|
|
|
+ this.$msg('网络异常')
|
|
|
})
|
|
|
},
|
|
|
requestCodePay(authCode){
|
|
|
// 如果取消对话框打开,则不处理扫码
|
|
|
- if(this.isCancelDialogOpen){
|
|
|
- this.$msg('请先完成取消操作')
|
|
|
+ if(this.isCancelDialogOpen || this.isCloseDialogOpen){
|
|
|
+ this.$msg('请先完成当前操作')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -169,6 +267,7 @@ export default {
|
|
|
if(res.code == 1){
|
|
|
if(res.data.returnStatus == 'SUCCESS'){
|
|
|
that.isPaymentProcessing = false
|
|
|
+ that.closeManualInput()
|
|
|
that.showSuccess = true
|
|
|
setTimeout(function(){
|
|
|
that.payFinish()
|
|
|
@@ -201,7 +300,7 @@ export default {
|
|
|
},
|
|
|
/**
|
|
|
* 启动自动后台付款复查定时器
|
|
|
- * 职责:降低轮询频率至4秒,且将次数限为4次(总计16秒),极大地减轻服务器压力的同时保证业务完整
|
|
|
+ * 职责:降低轮询频率至4秒,且将次数限为3次(总计12秒),极大地减轻服务器压力的同时保证业务完整
|
|
|
*/
|
|
|
startPolling(){
|
|
|
this.clearPolling()
|
|
|
@@ -212,7 +311,7 @@ export default {
|
|
|
let that = this
|
|
|
this.pollingTimer = setInterval(() => {
|
|
|
that.pollingCount++
|
|
|
- if (that.pollingCount > 4) {
|
|
|
+ if (that.pollingCount > 3) {
|
|
|
// 超时未确认,停止轮询,允许商户手动操作或重新扫码
|
|
|
that.clearPolling()
|
|
|
that.isPaymentProcessing = false
|
|
|
@@ -222,7 +321,7 @@ export default {
|
|
|
}
|
|
|
|
|
|
// 界面上展示当前自动复查的进度,提升收银员体验
|
|
|
- that.remindText = `正在自动复查支付结果 (第 ${that.pollingCount}/4 次)...`
|
|
|
+ that.remindText = `正在自动复查支付结果 (第 ${that.pollingCount}/3 次)...`
|
|
|
|
|
|
codePayVerify({id:that.orderId}).then(res=>{
|
|
|
if(res.code == 1){
|
|
|
@@ -252,10 +351,37 @@ export default {
|
|
|
this.clearPolling()
|
|
|
this.$emit('payFinish')
|
|
|
},
|
|
|
- cancelPayFn(){
|
|
|
+ /**
|
|
|
+ * 右上角关闭:先弹框确认,确认后仅关闭弹层不取消订单
|
|
|
+ */
|
|
|
+ closePayPanel(){
|
|
|
+ this.isCloseDialogOpen = true
|
|
|
+ this.$refs.closePaymentRef.open('center')
|
|
|
+ },
|
|
|
+ returnClose(){
|
|
|
+ this.isCloseDialogOpen = false
|
|
|
+ this.$refs.closePaymentRef.close()
|
|
|
+ },
|
|
|
+ confirmClose(){
|
|
|
+ this.isCloseDialogOpen = false
|
|
|
+ this.$refs.closePaymentRef.close()
|
|
|
+ this.clearPolling()
|
|
|
+ this.isPaymentProcessing = false
|
|
|
+ this.isCancelDialogOpen = false
|
|
|
+ this.closeManualInput()
|
|
|
+ if (this.showSuccess) {
|
|
|
+ this.payFinish()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.$emit('closePay')
|
|
|
+ },
|
|
|
+ cancelPayFn(force = 0){
|
|
|
+ this.isForceCancel = force
|
|
|
this.isCancelDialogOpen = true
|
|
|
if(this.returnCode=='BBS11105' || this.returnCode=='BBS10000'){
|
|
|
- this.cancelText = '确认取消?'
|
|
|
+ this.cancelText = this.isForceCancel ? '确认要强制取消?' : '确认取消?'
|
|
|
+ } else {
|
|
|
+ this.cancelText = this.isForceCancel ? '确认要强制取消?' : '确认要取消?'
|
|
|
}
|
|
|
this.$refs.cancelPaymentRef.open('center')
|
|
|
},
|
|
|
@@ -274,7 +400,7 @@ export default {
|
|
|
this.isCancelDialogOpen = false
|
|
|
this.clearPolling() // 取消时,必须清除定时器
|
|
|
this.isPaymentProcessing = false
|
|
|
- cancelOrderFn({id:this.orderId}).then(res=>{
|
|
|
+ cancelOrderFn({id:this.orderId,cashier:1, forceCancel: this.isForceCancel}).then(res=>{
|
|
|
if(res.code == 1){
|
|
|
that.$msg('已取消')
|
|
|
}
|
|
|
@@ -297,6 +423,17 @@ export default {
|
|
|
flex-direction: column;
|
|
|
box-sizing: border-box;
|
|
|
width:60vw;
|
|
|
+ position: relative;
|
|
|
+ & .close-icon {
|
|
|
+ position: absolute;
|
|
|
+ top: 10upx;
|
|
|
+ right: 10upx;
|
|
|
+ z-index: 10;
|
|
|
+ font-size: 40upx;
|
|
|
+ line-height: 1;
|
|
|
+ color: #999999;
|
|
|
+ padding: 0 8upx;
|
|
|
+ }
|
|
|
& .price-box {
|
|
|
min-height: 100upx;
|
|
|
& > .title {
|
|
|
@@ -330,6 +467,53 @@ export default {
|
|
|
font-size: 19upx;
|
|
|
margin: 10upx 0 10upx 0;
|
|
|
font-weight:bold;
|
|
|
+ color: #333333;
|
|
|
+ }
|
|
|
+ & .manual-code-box{
|
|
|
+ width: 100%;
|
|
|
+ margin: 10upx auto 0 auto;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ & .manual-code-input{
|
|
|
+ width: 50%;
|
|
|
+ height: 40upx;
|
|
|
+ line-height: 40upx;
|
|
|
+ border: 1upx solid #cccccc;
|
|
|
+ border-radius: 8upx;
|
|
|
+ font-size: 20upx;
|
|
|
+ text-align: center;
|
|
|
+ color: #333333;
|
|
|
+ background-color: #fafafa;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ & .manual-code-actions{
|
|
|
+ margin-top: 12upx;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ & .manual-btn{
|
|
|
+ height: 44upx;
|
|
|
+ min-width: 88upx;
|
|
|
+ padding: 0 16upx;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 44upx;
|
|
|
+ border: 1upx solid #dddddd;
|
|
|
+ border-radius: 22upx;
|
|
|
+ font-size: 14upx;
|
|
|
+ color: #666666;
|
|
|
+ background-color: #ffffff;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+ & .manual-btn + .manual-btn{
|
|
|
+ margin-left: 16upx;
|
|
|
+ }
|
|
|
+ & .manual-btn-primary{
|
|
|
+ background-color: #3385ff;
|
|
|
+ color: #ffffff;
|
|
|
+ border-color: #3385ff;
|
|
|
}
|
|
|
& .paying{
|
|
|
font-size: 19upx;
|