Files
pj_h5_cloud/src/pages/pay/scanPay.vue
T
2026-05-26 18:24:41 +08:00

293 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<crud-layout mainIsScroll bg="/static/page/blue-bg.png">
<view class="flex flex-col">
<view class="mx-auto text-34 py-2 h-80rpx">{{ scanPayState.mchName }}</view>
<tm-sheet :margin="[0, 10]" class="flex flex-col ">
<view class="flex flex-row items-end">
<span class="text-2xl text-[#333] mr-1 line-height-normal">¥</span>
<span class="text-3xl font-medium text-[#333]">{{ displayAmount }}</span>
</view>
<view v-if="couponDiscountAmount > 0" class="mt-10rpx flex items-center text-24rpx">
<view class="px-12rpx py-4rpx rounded-full bg-#0094ff1a c-#0094ff font-600">
已优惠 ¥{{ couponDiscountDisplay }}
</view>
<view class="ml-12rpx c-#666">
券后实付
<text class="c-#0094ff font-600">¥{{ couponPayableDisplay }}</text>
</view>
</view>
<view v-else-if="selectCoupon.id && couponUnavailableText" class="mt-10rpx text-24rpx c-#999">
{{ couponUnavailableText }}
</view>
</tm-sheet>
<tm-sheet v-if="walletList.length > 0" :margin="[0, 10]" class="flex flex-col">
<view class="text-gray-700 font-bold text-28 mb-1">选择支付钱包:</view>
<view v-for="(v, index) in walletList" :key="index"
class="border-4rpx border-solid border-#efefef my-8rpx duration-200 flex justify-between items-center p-18rpx rounded-2"
:class="{ 'border-4rpx border-solid border-blue!': selectWallet.id == v.id }"
@click="selectWallet = v">
<view class="flex items-center">
<image class="w-50 h-50 mr-8rpx" :src="filePath(v.walletTypeIcon)" />
<view class="text-32">{{ v.walletTypeName }}</view>
</view>
<view class="text-28 flex items-center">
<view>余额:</view>
<text class="main-text-28">{{ hooks.useMoney(v.balance) }} 元</text>
</view>
</view>
</tm-sheet>
<tm-sheet v-if="actCouponList.length > 0" :margin="[0, 10]" class="flex flex-col">
<view class="text-gray-700 font-bold text-28 mb-1">选择优惠券:</view>
<select-coupon-card :selectId="selectCoupon.id" v-for="item in actCouponList" :key="item.id"
:detail="item" :disabled="!isCouponAvailable(item)" @do-select="doSelectActCoupon" />
</tm-sheet>
<tm-button class="mt-1" :block="true" @click="handlePay()" :disabled="loading">支 付</tm-button>
<view class="h-120rpx" />
<number-keyboard v-if="numberKeyboardShow" v-model="amount" />
</view>
</crud-layout>
</template>
<script setup lang="ts">
import { ref, computed, reactive, watch } from "vue"
import { onLoad, onShow } from "@dcloudio/uni-app"
import payApi from "@@/api/pay"
import { merchantApi, orderApi, walletApi, actCouponApi } from "@/sheep/api"
import { hooks } from "@@/index"
import { router } from "@/sheep"
import { filePath } from "@/sheep/utils/file"
import { displayPrice, storePrice } from "@/sheep/utils/formatMoney"
import { usePayStore } from "@/sheep/stores/pay"
import { isEmpty } from "radash"
import NumberKeyboard from "@/sheep/components/number-keyboard.vue"
import SelectCouponCard from "./components/select-coupon-card.vue"
const loading = ref(false)
const scanPayState = reactive({
mchId: "", // 商户id
mchName: "", // 商户名
orderId: "", // 订单id
action: "", // action === "harQrCodeScanPay":扫收款机上生成的码支付
})
const walletList = ref<any>([]) // 钱包列表
const actCouponList = ref<any>([]) // 优惠券列表
const selectWallet = ref<any>({}) // 当前选中的钱包
const selectCoupon = ref<any>({}) // 当前选中的优惠券
const orderInfo = ref<any>({}) // 订单信息
const amount = ref("") // 键盘输入的金额
const numberKeyboardShow = ref(true)
onLoad(async (options: any) => {
uni.showLoading({ title: "加载中..." })
const { mchId, orderId, action } = options
if (action === "harQrCodeScanPay") {
if (!options.mchId || !options.orderId) return uni.showToast({ title: "扫码参数错误", icon: "none" })
scanPayState.mchId = mchId
scanPayState.orderId = orderId
scanPayState.action = action
numberKeyboardShow.value = false
await getMchDetail(mchId)
await getOrderInfo()
const order = orderInfo.value
amount.value = displayPrice(order.totalPrice || 0 + order.extraPrice || 0, "")
return
}
const { id } = options
if (!isEmpty(id)) {
scanPayState.mchId = id
await getMchDetail(id)
return
}
return uni.showToast({ title: "扫码参数错误", icon: "none" })
})
onShow(async () => {
try {
await getWalletList()
await getActCouponApi()
} finally {
uni.hideLoading()
}
})
// 获取商户名
const getMchDetail = async (mchId: string) => {
if (!mchId) return uni.showToast({ title: "商户id错误", icon: "none" })
const { data } = await merchantApi.detail({ id: mchId })
scanPayState.mchName = data.name
}
const doSelectActCoupon = (e: any) => {
const { detail } = e
if (!isCouponAvailable(detail)) {
uni.showToast({ title: `当前优惠券满${priceCentToYuan(detail.minPoint).toFixed(2)}元可用`, icon: "none" })
return
}
// 如果id一样就取消选择
if (selectCoupon.value?.id == detail.id) {
selectCoupon.value = {}
return
}
selectCoupon.value = detail
}
// 获取优惠券
const getActCouponApi = async () => {
try {
const { code, data }: any = await actCouponApi.mchList({
scopeId: scanPayState.mchId
})
if (code !== 0) return
actCouponList.value = data ?? []
if (actCouponList.value.length > 0) {
selectCoupon.value = actCouponList.value.find((item: any) => isCouponAvailable(item)) || {}
}
} catch (error) {
console.log("获取优惠券意外的错误error:", error);
}
}
// 获取钱包列表
const getWalletList = async () => {
const { code, data } = await walletApi.all()
if (code !== 0) return uni.showToast({ title: "获取钱包列表失败", icon: "none" })
walletList.value = data.records
if (walletList.value.length > 0) {
selectWallet.value = walletList.value.find((v: any) => v.walletTypeUniqueCode == "1")
}
}
// 获取订单信息
const getOrderInfo = async () => {
const { code, data }: any = await orderApi.detail({ id: scanPayState.orderId })
if (code !== 0) return uni.showToast({ title: "获取订单信息失败", icon: "none" })
orderInfo.value = data
}
// 格式化金额显示
const displayAmount = computed(() => {
if (!amount.value) return "0"
return amount.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
})
const amountNumber = computed(() => {
const value = Number(String(amount.value || 0).replace(/,/g, ""))
return Number.isNaN(value) ? 0 : value
})
const priceCentToYuan = (price: any) => {
return Number(displayPrice(Number(price || 0), "", 2))
}
const isCouponAvailable = (coupon: any) => {
if (!coupon) return false
const minPoint = priceCentToYuan(coupon.minPoint)
if (minPoint > 0 && amountNumber.value < minPoint) return false
return true
}
const couponPayableAmount = computed(() => {
if (!selectCoupon.value?.id || amountNumber.value <= 0) return amountNumber.value
const couponType = String(selectCoupon.value.couponType || "")
const minPoint = priceCentToYuan(selectCoupon.value.minPoint)
const useAmount = priceCentToYuan(selectCoupon.value.useAmount)
// 满减券: 不足门槛不优惠, 否则按 useAmount 作为券后金额
if (couponType === "1") {
const faceValue = priceCentToYuan(selectCoupon.value.faceValue)
if (minPoint > 0 && amountNumber.value < minPoint) return amountNumber.value
const payable = useAmount > 0 ? useAmount : amountNumber.value - faceValue
return Math.max(0, Number(payable.toFixed(2)))
}
// 折扣券: 不足门槛不优惠, totalPrice * faceValue / 100
if (couponType === "2") {
const faceValue = Number(selectCoupon.value.faceValue || 0)
if (minPoint > 0 && amountNumber.value < minPoint) return amountNumber.value
const _p = Math.max(0, Number(((amountNumber.value * (100 - faceValue)) / 100).toFixed(2)))
return _p
}
// 立减券: 按 useAmount 作为券后金额
if (couponType === "3") {
const faceValue = priceCentToYuan(selectCoupon.value.faceValue)
const payable = useAmount > 0 ? useAmount : amountNumber.value - faceValue
return Math.max(0, Number(payable.toFixed(2)))
}
return amountNumber.value
})
const couponDiscountAmount = computed(() => {
return Math.max(0, Number((amountNumber.value - couponPayableAmount.value).toFixed(2)))
})
const couponDiscountDisplay = computed(() => couponDiscountAmount.value.toFixed(2))
const couponPayableDisplay = computed(() => couponPayableAmount.value.toFixed(2))
const couponUnavailableText = computed(() => {
if (!selectCoupon.value?.id || amountNumber.value <= 0) return ""
const minPoint = priceCentToYuan(selectCoupon.value.minPoint)
if (minPoint > 0 && amountNumber.value < minPoint) {
return `当前优惠券满${minPoint.toFixed(2)}元可用`
}
return ""
})
watch(amountNumber, () => {
if (selectCoupon.value?.id && !isCouponAvailable(selectCoupon.value)) {
selectCoupon.value = {}
}
})
// 点击支付
const handlePay = () => {
if (!scanPayState.mchId) return uni.showToast({ title: "扫码参数不存在", icon: "none" })
if (!amount.value) return uni.showToast({ title: "请输入金额", icon: "none" })
if (Number(amount.value) === 0) return uni.showToast({ title: "金额不能为0", icon: "none" })
uni.showModal({
title: "确定支付吗?",
success: async (res) => {
if (res.confirm) {
await pay()
}
},
})
}
// 支付
const pay = async () => {
try {
uni.showLoading({ title: "支付中...", mask: true })
if (scanPayState.action === "harQrCodeScanPay") {
const { code, msg }: any = await orderApi.doneHardQrCodeScanOrder({
orderId: scanPayState.orderId,
walletTypeId: selectWallet.value.walletTypeId,
})
if (code != 0) return uni.showToast({ title: msg || "支付失败", icon: "error" })
} else {
const { code, msg } = await payApi.createQrCodeOrder({
merchantId: scanPayState.mchId,
customPrice: storePrice(Number(amount.value)),
walletId: selectWallet.value.id,
actCouponRecordId: selectCoupon.value?.id || "",
})
if (code != 0) return uni.showToast({ title: msg || "支付失败", icon: "error" })
}
uni.hideLoading()
usePayStore().setScanPayData({
mchId: scanPayState.mchId,
mchName: scanPayState.mchName,
payPrice: couponPayableDisplay.value ?? Number(amount.value).toFixed(2),
})
router.jump("/pages/pay/payComplete", {}, 2)
} catch (error: any) {
uni.showToast({ title: "请求错误", icon: "error" })
uni.hideLoading()
}
}
</script>