支付
This commit is contained in:
@@ -2,15 +2,20 @@ import { useCallback, useEffect, useState } from 'react'
|
|||||||
import Taro, { useRouter } from '@tarojs/taro'
|
import Taro, { useRouter } from '@tarojs/taro'
|
||||||
import { View, Text, Image } from '@tarojs/components'
|
import { View, Text, Image } from '@tarojs/components'
|
||||||
import { Empty } from '@antmjs/vantui'
|
import { Empty } from '@antmjs/vantui'
|
||||||
import { getPaymentDetailApi, PAY_METHOD_NAMES, PAY_STATUS_NAMES } from '@/services/payment'
|
import {
|
||||||
|
getPaymentDetailApi,
|
||||||
|
getPayStatusName,
|
||||||
|
PAY_METHOD_NAMES,
|
||||||
|
queryOnlinePaymentApi,
|
||||||
|
} from '@/services/payment'
|
||||||
import { resolveFileUrl } from '@/utils/format'
|
import { resolveFileUrl } from '@/utils/format'
|
||||||
import type { PaymentDetail } from '@/services/payment'
|
import type { PaymentDetail } from '@/services/payment'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 支付详情页
|
* 支付详情页
|
||||||
* 支付信息 + 汇款凭证(可预览)+ 合并付款的账单(可下钻账单详情)
|
* 线下凭证单:支付信息 + 汇款凭证(可预览)+ 合并付款的账单(可下钻账单详情),审核拒绝后可重新发起付款
|
||||||
* 已拒绝时底部提供「重新发起付款」(账单已由后台释放,可重新合并提交)
|
* 在线支付单:无凭证,待支付时可「刷新支付结果」主动同步网关结果(后台通知延迟/丢失时的兜底)
|
||||||
*/
|
*/
|
||||||
export default function PaymentDetailPage() {
|
export default function PaymentDetailPage() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -18,16 +23,25 @@ export default function PaymentDetailPage() {
|
|||||||
|
|
||||||
const [detail, setDetail] = useState<PaymentDetail | null>(null)
|
const [detail, setDetail] = useState<PaymentDetail | null>(null)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [syncing, setSyncing] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
const loadDetail = useCallback(async () => {
|
||||||
if (!id) return
|
if (!id) return
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
getPaymentDetailApi(id)
|
try {
|
||||||
.then(res => setDetail(res.data))
|
const res = await getPaymentDetailApi(id)
|
||||||
.catch(() => {})
|
setDetail(res.data)
|
||||||
.finally(() => setLoading(false))
|
} catch {
|
||||||
|
// 错误已由 request 层 toast
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDetail()
|
||||||
|
}, [loadDetail])
|
||||||
|
|
||||||
/** 预览凭证图片 */
|
/** 预览凭证图片 */
|
||||||
const previewVoucher = useCallback((current: string) => {
|
const previewVoucher = useCallback((current: string) => {
|
||||||
const urls = (detail?.payment.voucher_urls ?? []).map(resolveFileUrl)
|
const urls = (detail?.payment.voucher_urls ?? []).map(resolveFileUrl)
|
||||||
@@ -39,13 +53,35 @@ export default function PaymentDetailPage() {
|
|||||||
Taro.navigateTo({ url: `/pages/bill-detail/index?id=${billId}` })
|
Taro.navigateTo({ url: `/pages/bill-detail/index?id=${billId}` })
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
/** 已拒绝 → 携带本组账单重新发起付款 */
|
/** 已拒绝 / 支付失败 → 携带本组账单重新发起付款(账单已由后台释放) */
|
||||||
const handleRepay = useCallback(() => {
|
const handleRepay = useCallback(() => {
|
||||||
if (!detail) return
|
if (!detail) return
|
||||||
const ids = detail.bills.map(b => b.id).join(',')
|
const ids = detail.bills.map(b => b.id).join(',')
|
||||||
Taro.redirectTo({ url: `/pages/payment/index?ids=${ids}` })
|
Taro.redirectTo({ url: `/pages/payment/index?ids=${ids}` })
|
||||||
}, [detail])
|
}, [detail])
|
||||||
|
|
||||||
|
/** 在线支付待支付 → 主动查询网关同步结果(已支付则后端立即结账),随后刷新详情 */
|
||||||
|
const handleSync = useCallback(async () => {
|
||||||
|
if (!detail || syncing) return
|
||||||
|
setSyncing(true)
|
||||||
|
try {
|
||||||
|
const res = await queryOnlinePaymentApi(detail.payment.payment_no)
|
||||||
|
if (res.data.status === 1) {
|
||||||
|
Taro.showToast({ title: '支付成功', icon: 'success' })
|
||||||
|
loadDetail()
|
||||||
|
} else if (res.data.status === 2) {
|
||||||
|
Taro.showToast({ title: '支付失败,账单已释放', icon: 'none' })
|
||||||
|
loadDetail()
|
||||||
|
} else {
|
||||||
|
Taro.showToast({ title: '暂未查询到支付结果,请稍后再试', icon: 'none' })
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 错误已由 request 层 toast
|
||||||
|
} finally {
|
||||||
|
setSyncing(false)
|
||||||
|
}
|
||||||
|
}, [detail, syncing, loadDetail])
|
||||||
|
|
||||||
if (loading && !detail) {
|
if (loading && !detail) {
|
||||||
return <View className='pay-detail'><Empty description='加载中...' /></View>
|
return <View className='pay-detail'><Empty description='加载中...' /></View>
|
||||||
}
|
}
|
||||||
@@ -55,6 +91,8 @@ export default function PaymentDetailPage() {
|
|||||||
|
|
||||||
const { payment, bills } = detail
|
const { payment, bills } = detail
|
||||||
const vouchers = payment.voucher_urls.map(resolveFileUrl)
|
const vouchers = payment.voucher_urls.map(resolveFileUrl)
|
||||||
|
/** 在线支付单(旺铺网关):状态语义与线下凭证单不同,无凭证 */
|
||||||
|
const isOnline = payment.pay_type === 2
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className={`pay-detail ${payment.status === 2 ? 'pay-detail--reject' : ''}`}>
|
<View className={`pay-detail ${payment.status === 2 ? 'pay-detail--reject' : ''}`}>
|
||||||
@@ -63,16 +101,23 @@ export default function PaymentDetailPage() {
|
|||||||
<View className='pay-card__header'>
|
<View className='pay-card__header'>
|
||||||
<Text className='pay-card__no'>{payment.payment_no}</Text>
|
<Text className='pay-card__no'>{payment.payment_no}</Text>
|
||||||
<Text className={`pay-card__status pay-card__status--${payment.status}`}>
|
<Text className={`pay-card__status pay-card__status--${payment.status}`}>
|
||||||
{PAY_STATUS_NAMES[payment.status]}
|
{getPayStatusName(payment)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text className='pay-card__amount'>¥{payment.amount}</Text>
|
<Text className='pay-card__amount'>¥{payment.amount}</Text>
|
||||||
{payment.status === 0 && (
|
{payment.status === 0 && !isOnline && (
|
||||||
<Text className='pay-card__tip'>付款申请已提交,商家审核通过后账单将置为已支付</Text>
|
<Text className='pay-card__tip'>付款申请已提交,商家审核通过后账单将置为已支付</Text>
|
||||||
)}
|
)}
|
||||||
|
{payment.status === 0 && isOnline && (
|
||||||
|
<Text className='pay-card__tip'>
|
||||||
|
账单已锁定,等待支付结果确认;如已完成支付,可点击下方「刷新支付结果」
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
{payment.status === 2 && (
|
{payment.status === 2 && (
|
||||||
<Text className='pay-card__tip pay-card__tip--reject'>
|
<Text className='pay-card__tip pay-card__tip--reject'>
|
||||||
审核未通过{payment.audit_remark ? `:${payment.audit_remark}` : ''},账单已释放,可重新发起付款
|
{isOnline
|
||||||
|
? '支付失败,账单已释放,可重新发起付款'
|
||||||
|
: `审核未通过${payment.audit_remark ? `:${payment.audit_remark}` : ''},账单已释放,可重新发起付款`}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
<View className='pay-card__row'>
|
<View className='pay-card__row'>
|
||||||
@@ -89,6 +134,18 @@ export default function PaymentDetailPage() {
|
|||||||
<Text className='pay-card__value'>{payment.audited_at}</Text>
|
<Text className='pay-card__value'>{payment.audited_at}</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
{isOnline && payment.paid_at && (
|
||||||
|
<View className='pay-card__row'>
|
||||||
|
<Text className='pay-card__label'>支付时间</Text>
|
||||||
|
<Text className='pay-card__value'>{payment.paid_at}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{isOnline && payment.trade_no && (
|
||||||
|
<View className='pay-card__row'>
|
||||||
|
<Text className='pay-card__label'>交易单号</Text>
|
||||||
|
<Text className='pay-card__value'>{payment.trade_no}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
{payment.remark && (
|
{payment.remark && (
|
||||||
<View className='pay-card__row'>
|
<View className='pay-card__row'>
|
||||||
<Text className='pay-card__label'>付款备注</Text>
|
<Text className='pay-card__label'>付款备注</Text>
|
||||||
@@ -97,22 +154,24 @@ export default function PaymentDetailPage() {
|
|||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ===== 汇款凭证 ===== */}
|
{/* ===== 汇款凭证(在线支付单无凭证) ===== */}
|
||||||
<View className='pay-card'>
|
{!isOnline && (
|
||||||
<Text className='pay-section__title'>汇款凭证({vouchers.length})</Text>
|
<View className='pay-card'>
|
||||||
<View className='pay-vouchers'>
|
<Text className='pay-section__title'>汇款凭证({vouchers.length})</Text>
|
||||||
{vouchers.map((url, i) => (
|
<View className='pay-vouchers'>
|
||||||
<Image
|
{vouchers.map((url, i) => (
|
||||||
key={i}
|
<Image
|
||||||
className='pay-vouchers__img'
|
key={i}
|
||||||
src={url}
|
className='pay-vouchers__img'
|
||||||
mode='aspectFill'
|
src={url}
|
||||||
onClick={() => previewVoucher(url)}
|
mode='aspectFill'
|
||||||
/>
|
onClick={() => previewVoucher(url)}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
{vouchers.length === 0 && <Empty description='暂无凭证图片' />}
|
||||||
</View>
|
</View>
|
||||||
{vouchers.length === 0 && <Empty description='暂无凭证图片' />}
|
)}
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* ===== 合并账单 ===== */}
|
{/* ===== 合并账单 ===== */}
|
||||||
<View className='pay-card'>
|
<View className='pay-card'>
|
||||||
@@ -132,12 +191,21 @@ export default function PaymentDetailPage() {
|
|||||||
{bills.length === 0 && <Empty description='暂无关联账单' />}
|
{bills.length === 0 && <Empty description='暂无关联账单' />}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ===== 已拒绝 → 重新付款 ===== */}
|
{/* ===== 已拒绝 / 支付失败 → 重新付款 ===== */}
|
||||||
{payment.status === 2 && (
|
{payment.status === 2 && (
|
||||||
<View className='pay-bar'>
|
<View className='pay-bar'>
|
||||||
<View className='pay-bar__btn' onClick={handleRepay}>重新发起付款</View>
|
<View className='pay-bar__btn' onClick={handleRepay}>重新发起付款</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* ===== 在线支付待支付 → 主动同步支付结果 ===== */}
|
||||||
|
{isOnline && payment.status === 0 && (
|
||||||
|
<View className='pay-bar'>
|
||||||
|
<View className='pay-bar__btn' onClick={handleSync}>
|
||||||
|
{syncing ? '查询中...' : '刷新支付结果'}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Taro, { useDidShow, useReachBottom } from '@tarojs/taro'
|
|||||||
import { View, Text, ScrollView } from '@tarojs/components'
|
import { View, Text, ScrollView } from '@tarojs/components'
|
||||||
import { Empty } from '@antmjs/vantui'
|
import { Empty } from '@antmjs/vantui'
|
||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||||
import { getPaymentListApi, PAY_METHOD_NAMES, PAY_STATUS_NAMES } from '@/services/payment'
|
import { getPaymentListApi, getPayStatusName, PAY_METHOD_NAMES } from '@/services/payment'
|
||||||
import type { Payment, PayStatus } from '@/services/payment'
|
import type { Payment, PayStatus } from '@/services/payment'
|
||||||
import './index.less'
|
import './index.less'
|
||||||
|
|
||||||
@@ -115,7 +115,7 @@ export default function PaymentRecordsPage() {
|
|||||||
<View className='payment-item__header'>
|
<View className='payment-item__header'>
|
||||||
<Text className='payment-item__no'>{record.payment_no}</Text>
|
<Text className='payment-item__no'>{record.payment_no}</Text>
|
||||||
<Text className={`payment-item__status payment-item__status--${record.status}`}>
|
<Text className={`payment-item__status payment-item__status--${record.status}`}>
|
||||||
{PAY_STATUS_NAMES[record.status]}
|
{getPayStatusName(record)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className='payment-item__body'>
|
<View className='payment-item__body'>
|
||||||
@@ -128,7 +128,7 @@ export default function PaymentRecordsPage() {
|
|||||||
<Text className='payment-item__bills'>合并 {record.bills_count ?? 0} 张账单</Text>
|
<Text className='payment-item__bills'>合并 {record.bills_count ?? 0} 张账单</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{record.status === 2 && !!record.audit_remark && (
|
{record.status === 2 && record.pay_type !== 2 && !!record.audit_remark && (
|
||||||
<Text className='payment-item__reject'>拒绝原因:{record.audit_remark}</Text>
|
<Text className='payment-item__reject'>拒绝原因:{record.audit_remark}</Text>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
+135
-36
@@ -4,7 +4,7 @@ import { View, Text, Image, Textarea } from '@tarojs/components'
|
|||||||
import { Empty, Icon } from '@antmjs/vantui'
|
import { Empty, Icon } from '@antmjs/vantui'
|
||||||
import useAuthStore from '@/stores/auth/useAuthStore'
|
import useAuthStore from '@/stores/auth/useAuthStore'
|
||||||
import { getBillListApi } from '@/services/bill'
|
import { getBillListApi } from '@/services/bill'
|
||||||
import { createPaymentApi, getPaymentConfigApi } from '@/services/payment'
|
import { createOnlinePaymentApi, createPaymentApi, getPaymentConfigApi, queryOnlinePaymentApi } from '@/services/payment'
|
||||||
import { chooseAndUploadImages } from '@/utils/upload'
|
import { chooseAndUploadImages } from '@/utils/upload'
|
||||||
import { resolveFileUrl } from '@/utils/format'
|
import { resolveFileUrl } from '@/utils/format'
|
||||||
import type { Bill } from '@/services/bill'
|
import type { Bill } from '@/services/bill'
|
||||||
@@ -17,8 +17,14 @@ const PAGE_SIZE = 20
|
|||||||
/** 凭证最多上传张数 */
|
/** 凭证最多上传张数 */
|
||||||
const MAX_VOUCHERS = 3
|
const MAX_VOUCHERS = 3
|
||||||
|
|
||||||
/** 支付方式选项 */
|
/** 在线支付(调起 wx.requestPayment)仅微信小程序支持 */
|
||||||
|
const IS_WEAPP = process.env.TARO_ENV === 'weapp'
|
||||||
|
|
||||||
|
/** 支付方式选项(在线支付仅小程序端展示,排在最前) */
|
||||||
const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc: string }> = [
|
const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc: string }> = [
|
||||||
|
...(IS_WEAPP
|
||||||
|
? [{ value: 4 as PayMethod, label: '微信在线支付', icon: 'wechat', desc: '小程序内直接付款,免上传凭证' }]
|
||||||
|
: []),
|
||||||
{ value: 1, label: '微信支付', icon: 'wechat', desc: '扫码完成转账' },
|
{ value: 1, label: '微信支付', icon: 'wechat', desc: '扫码完成转账' },
|
||||||
{ value: 2, label: '支付宝', icon: 'alipay', desc: '扫码完成转账' },
|
{ value: 2, label: '支付宝', icon: 'alipay', desc: '扫码完成转账' },
|
||||||
{ value: 3, label: '对公汇款', icon: 'credit-pay', desc: '银行转账至对公账户' },
|
{ value: 3, label: '对公汇款', icon: 'credit-pay', desc: '银行转账至对公账户' },
|
||||||
@@ -26,7 +32,9 @@ const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc:
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发起付款页(合并付款)
|
* 发起付款页(合并付款)
|
||||||
* 选择本店可付款账单(?payable=1)→ 选择支付方式(展示收款码 / 对公账户)→ 上传汇款凭证 → 提交,后台审核
|
* 选择本店可付款账单(?payable=1)→ 选择支付方式 → 提交:
|
||||||
|
* - 在线支付(仅小程序):wx.login 取 code → 后端经旺铺网关下单 → 调起微信支付 → 主动查询同步结果(回调兜底)
|
||||||
|
* - 线下凭证:展示收款码 / 对公账户 → 上传汇款凭证 → 提交,后台审核
|
||||||
* 支持 ?ids=1,2 预选账单(账单详情页"去付款"跳转)
|
* 支持 ?ids=1,2 预选账单(账单详情页"去付款"跳转)
|
||||||
*/
|
*/
|
||||||
export default function PaymentPage() {
|
export default function PaymentPage() {
|
||||||
@@ -50,12 +58,15 @@ export default function PaymentPage() {
|
|||||||
const loadingRef = useRef(false)
|
const loadingRef = useRef(false)
|
||||||
|
|
||||||
const [config, setConfig] = useState<PaymentConfig | null>(null)
|
const [config, setConfig] = useState<PaymentConfig | null>(null)
|
||||||
const [payMethod, setPayMethod] = useState<PayMethod>(1)
|
const [payMethod, setPayMethod] = useState<PayMethod>(IS_WEAPP ? 4 : 1)
|
||||||
const [vouchers, setVouchers] = useState<UploadedFile[]>([])
|
const [vouchers, setVouchers] = useState<UploadedFile[]>([])
|
||||||
const [remark, setRemark] = useState('')
|
const [remark, setRemark] = useState('')
|
||||||
const [uploading, setUploading] = useState(false)
|
const [uploading, setUploading] = useState(false)
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
|
||||||
|
/** 在线支付(旺铺网关 JSAPI):免凭证,调起微信支付 */
|
||||||
|
const isOnline = payMethod === 4
|
||||||
|
|
||||||
/** 拉取可付款账单(首次加载应用路由预选) */
|
/** 拉取可付款账单(首次加载应用路由预选) */
|
||||||
const loadBills = useCallback(
|
const loadBills = useCallback(
|
||||||
async (pageNum: number, reset: boolean) => {
|
async (pageNum: number, reset: boolean) => {
|
||||||
@@ -152,8 +163,8 @@ export default function PaymentPage() {
|
|||||||
Taro.setClipboardData({ data: config.bank_info })
|
Taro.setClipboardData({ data: config.bank_info })
|
||||||
}, [config])
|
}, [config])
|
||||||
|
|
||||||
/** 提交付款申请 */
|
/** 提交线下凭证付款申请(后台审核) */
|
||||||
const handleSubmit = useCallback(async () => {
|
const handleVoucherSubmit = useCallback(async () => {
|
||||||
if (submitting) return
|
if (submitting) return
|
||||||
if (selectedIds.length === 0) {
|
if (selectedIds.length === 0) {
|
||||||
Taro.showToast({ title: '请选择要付款的账单', icon: 'none' })
|
Taro.showToast({ title: '请选择要付款的账单', icon: 'none' })
|
||||||
@@ -183,8 +194,94 @@ export default function PaymentPage() {
|
|||||||
}
|
}
|
||||||
}, [submitting, selectedIds, vouchers, payMethod, remark, loadBills])
|
}, [submitting, selectedIds, vouchers, payMethod, remark, loadBills])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在线支付:wx.login 取 code → 后端经旺铺网关下单 → 调起微信支付 → 主动查询同步结果
|
||||||
|
* 无论支付成功/取消都跳转支付详情(待支付单可在详情页刷新同步结果)
|
||||||
|
*/
|
||||||
|
const handleOnlinePay = useCallback(async () => {
|
||||||
|
if (submitting) return
|
||||||
|
if (!IS_WEAPP) {
|
||||||
|
Taro.showToast({ title: '请在微信小程序中使用在线支付', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (selectedIds.length === 0) {
|
||||||
|
Taro.showToast({ title: '请选择要付款的账单', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSubmitting(true)
|
||||||
|
try {
|
||||||
|
// 1. 获取微信登录凭证(后端换付款人 openid)
|
||||||
|
const { code } = await Taro.login()
|
||||||
|
if (!code) {
|
||||||
|
Taro.showToast({ title: '微信登录失败,请稍后重试', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 2. 后端下单(创建支付单并锁定账单)
|
||||||
|
const res = await createOnlinePaymentApi({
|
||||||
|
bill_ids: selectedIds,
|
||||||
|
code,
|
||||||
|
remark: remark.trim() || undefined,
|
||||||
|
})
|
||||||
|
const { id, payment_no, pay_params } = res.data
|
||||||
|
// 3. 调起微信支付(pay_params 为网关透传的调起参数)
|
||||||
|
try {
|
||||||
|
await Taro.requestPayment({
|
||||||
|
timeStamp: String(pay_params.timeStamp || ''),
|
||||||
|
nonceStr: String(pay_params.nonceStr || ''),
|
||||||
|
package: String(pay_params.package || ''),
|
||||||
|
signType: (pay_params.signType || 'RSA') as 'MD5' | 'HMAC-SHA256' | 'RSA',
|
||||||
|
paySign: String(pay_params.paySign || ''),
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
// 用户取消或调起失败:账单仍锁定在支付单中,进详情页可刷新同步/稍后处理
|
||||||
|
const errMsg = e?.errMsg || ''
|
||||||
|
Taro.showToast({
|
||||||
|
title: errMsg.includes('cancel') ? '已取消支付' : '支付调起失败,请稍后重试',
|
||||||
|
icon: 'none',
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.redirectTo({ url: `/pages/payment-detail/index?id=${id}` })
|
||||||
|
}, 800)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 4. 主动查询同步结果(网关后台通知延迟/丢失时的兜底结账)
|
||||||
|
let paid = false
|
||||||
|
try {
|
||||||
|
const q = await queryOnlinePaymentApi(payment_no)
|
||||||
|
paid = q.data.status === 1
|
||||||
|
} catch {
|
||||||
|
// 查询失败不阻断,进详情页可手动刷新
|
||||||
|
}
|
||||||
|
Taro.showToast({ title: paid ? '支付成功' : '支付结果确认中', icon: paid ? 'success' : 'none' })
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.redirectTo({ url: `/pages/payment-detail/index?id=${id}` })
|
||||||
|
}, 800)
|
||||||
|
} catch {
|
||||||
|
// 下单失败:账单可能已被其他端付款/锁定,刷新列表
|
||||||
|
loadBills(1, true)
|
||||||
|
} finally {
|
||||||
|
setSubmitting(false)
|
||||||
|
}
|
||||||
|
}, [submitting, selectedIds, remark, loadBills])
|
||||||
|
|
||||||
|
/** 提交入口:按支付方式分发 */
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
if (isOnline) {
|
||||||
|
handleOnlinePay()
|
||||||
|
} else {
|
||||||
|
handleVoucherSubmit()
|
||||||
|
}
|
||||||
|
}, [isOnline, handleOnlinePay, handleVoucherSubmit])
|
||||||
|
|
||||||
/** 当前支付方式的收款展示 */
|
/** 当前支付方式的收款展示 */
|
||||||
const renderMethodContent = () => {
|
const renderMethodContent = () => {
|
||||||
|
if (isOnline) {
|
||||||
|
return (
|
||||||
|
<Text className='pay-method__empty'>
|
||||||
|
确认支付后将调起微信支付,支付成功后账单自动结清
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
if (payMethod === 3) {
|
if (payMethod === 3) {
|
||||||
return config?.bank_info ? (
|
return config?.bank_info ? (
|
||||||
<View className='pay-method__content'>
|
<View className='pay-method__content'>
|
||||||
@@ -284,37 +381,39 @@ export default function PaymentPage() {
|
|||||||
{renderMethodContent()}
|
{renderMethodContent()}
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* ========== 汇款凭证 ========== */}
|
{/* ========== 汇款凭证(在线支付免凭证) ========== */}
|
||||||
<View className='pay-section'>
|
{!isOnline && (
|
||||||
<View className='pay-section__header'>
|
<View className='pay-section'>
|
||||||
<Text className='pay-section__title'>汇款凭证</Text>
|
<View className='pay-section__header'>
|
||||||
<Text className='pay-section__hint'>转账截图或回单,最多 {MAX_VOUCHERS} 张</Text>
|
<Text className='pay-section__title'>汇款凭证</Text>
|
||||||
</View>
|
<Text className='pay-section__hint'>转账截图或回单,最多 {MAX_VOUCHERS} 张</Text>
|
||||||
<View className='pay-vouchers'>
|
</View>
|
||||||
{vouchers.map((v, i) => {
|
<View className='pay-vouchers'>
|
||||||
const url = resolveFileUrl(v.url)
|
{vouchers.map((v, i) => {
|
||||||
return (
|
const url = resolveFileUrl(v.url)
|
||||||
<View key={v.id} className='pay-voucher'>
|
return (
|
||||||
<Image
|
<View key={v.id} className='pay-voucher'>
|
||||||
className='pay-voucher__img'
|
<Image
|
||||||
src={url}
|
className='pay-voucher__img'
|
||||||
mode='aspectFill'
|
src={url}
|
||||||
onClick={() => previewImage(vouchers.map(x => resolveFileUrl(x.url)), url)}
|
mode='aspectFill'
|
||||||
/>
|
onClick={() => previewImage(vouchers.map(x => resolveFileUrl(x.url)), url)}
|
||||||
<View className='pay-voucher__del' onClick={() => handleRemoveVoucher(i)}>
|
/>
|
||||||
<Icon name='cross' size={12} color='#fff' />
|
<View className='pay-voucher__del' onClick={() => handleRemoveVoucher(i)}>
|
||||||
|
<Icon name='cross' size={12} color='#fff' />
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
{vouchers.length < MAX_VOUCHERS && (
|
||||||
|
<View className='pay-voucher pay-voucher--add' onClick={handleAddVoucher}>
|
||||||
|
<Icon name={uploading ? 'more' : 'plus'} size={24} color='#969799' />
|
||||||
|
<Text className='pay-voucher__add-text'>{uploading ? '上传中' : '上传凭证'}</Text>
|
||||||
</View>
|
</View>
|
||||||
)
|
)}
|
||||||
})}
|
</View>
|
||||||
{vouchers.length < MAX_VOUCHERS && (
|
|
||||||
<View className='pay-voucher pay-voucher--add' onClick={handleAddVoucher}>
|
|
||||||
<Icon name={uploading ? 'more' : 'plus'} size={24} color='#969799' />
|
|
||||||
<Text className='pay-voucher__add-text'>{uploading ? '上传中' : '上传凭证'}</Text>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
)}
|
||||||
|
|
||||||
{/* ========== 备注 ========== */}
|
{/* ========== 备注 ========== */}
|
||||||
<View className='pay-section'>
|
<View className='pay-section'>
|
||||||
@@ -323,7 +422,7 @@ export default function PaymentPage() {
|
|||||||
className='pay-remark'
|
className='pay-remark'
|
||||||
value={remark}
|
value={remark}
|
||||||
maxlength={255}
|
maxlength={255}
|
||||||
placeholder='如:汇款人姓名、转账时间等'
|
placeholder={isOnline ? '可填写付款说明' : '如:汇款人姓名、转账时间等'}
|
||||||
onInput={e => setRemark(e.detail.value)}
|
onInput={e => setRemark(e.detail.value)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -339,7 +438,7 @@ export default function PaymentPage() {
|
|||||||
className={`pay-bar__btn ${selectedIds.length === 0 || submitting ? 'disabled' : ''}`}
|
className={`pay-bar__btn ${selectedIds.length === 0 || submitting ? 'disabled' : ''}`}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
{submitting ? '提交中...' : '提交付款'}
|
{submitting ? (isOnline ? '支付中...' : '提交中...') : isOnline ? '立即支付' : '提交付款'}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+73
-6
@@ -1,16 +1,20 @@
|
|||||||
import { get, post } from '@/utils/request'
|
import { get, post } from '@/utils/request'
|
||||||
import type { PaginatedData } from '@/types/api'
|
import type { PaginatedData } from '@/types/api'
|
||||||
|
|
||||||
/** 支付方式:1 微信 / 2 支付宝 / 3 对公汇款 */
|
/** 支付方式:1 微信 / 2 支付宝 / 3 对公汇款 / 4 旺铺支付(小程序在线支付) */
|
||||||
export type PayMethod = 1 | 2 | 3
|
export type PayMethod = 1 | 2 | 3 | 4
|
||||||
|
|
||||||
export const PAY_METHOD_NAMES: Record<PayMethod, string> = {
|
export const PAY_METHOD_NAMES: Record<PayMethod, string> = {
|
||||||
1: '微信支付',
|
1: '微信支付',
|
||||||
2: '支付宝',
|
2: '支付宝',
|
||||||
3: '对公汇款',
|
3: '对公汇款',
|
||||||
|
4: '微信在线支付',
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 支付状态:0 待审核 / 1 已通过 / 2 已拒绝 */
|
/** 支付类型:1 线下凭证支付 / 2 在线支付(旧数据可能缺省,缺省按线下处理) */
|
||||||
|
export type PayType = 1 | 2
|
||||||
|
|
||||||
|
/** 支付状态:0 待审核 / 1 已通过 / 2 已拒绝(线下凭证支付单语义) */
|
||||||
export type PayStatus = 0 | 1 | 2
|
export type PayStatus = 0 | 1 | 2
|
||||||
|
|
||||||
export const PAY_STATUS_NAMES: Record<PayStatus, string> = {
|
export const PAY_STATUS_NAMES: Record<PayStatus, string> = {
|
||||||
@@ -19,6 +23,20 @@ export const PAY_STATUS_NAMES: Record<PayStatus, string> = {
|
|||||||
2: '已拒绝',
|
2: '已拒绝',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 在线支付状态:0 待支付 / 1 支付成功 / 2 支付失败(与线下同字段,按 pay_type 区分语义) */
|
||||||
|
export type OnlinePayStatus = 0 | 1 | 2
|
||||||
|
|
||||||
|
export const ONLINE_PAY_STATUS_NAMES: Record<OnlinePayStatus, string> = {
|
||||||
|
0: '待支付',
|
||||||
|
1: '支付成功',
|
||||||
|
2: '支付失败',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 支付单状态展示名(在线支付单与线下凭证单同字段不同语义,按 pay_type 取名) */
|
||||||
|
export function getPayStatusName(payment: { status: PayStatus; pay_type?: PayType }): string {
|
||||||
|
return payment.pay_type === 2 ? ONLINE_PAY_STATUS_NAMES[payment.status] : PAY_STATUS_NAMES[payment.status]
|
||||||
|
}
|
||||||
|
|
||||||
/** 支付配置(付款页展示,图片为解析后的预览地址,可能为空串) */
|
/** 支付配置(付款页展示,图片为解析后的预览地址,可能为空串) */
|
||||||
export interface PaymentConfig {
|
export interface PaymentConfig {
|
||||||
wechat_qrcode: string
|
wechat_qrcode: string
|
||||||
@@ -35,17 +53,23 @@ export interface Payment {
|
|||||||
user_id: number
|
user_id: number
|
||||||
/** 合并付款总金额 */
|
/** 合并付款总金额 */
|
||||||
amount: string
|
amount: string
|
||||||
|
/** 支付类型:1 线下凭证 / 2 在线支付(旺铺网关) */
|
||||||
|
pay_type?: PayType
|
||||||
pay_method: PayMethod
|
pay_method: PayMethod
|
||||||
/** 凭证图片 ID 数组(模型 casts 为 array) */
|
/** 凭证图片 ID 数组(模型 casts 为 array,在线支付单为空) */
|
||||||
voucher_ids: number[]
|
voucher_ids: number[]
|
||||||
status: PayStatus
|
status: PayStatus
|
||||||
/** 提交备注 */
|
/** 提交备注 */
|
||||||
remark: string
|
remark: string
|
||||||
/** 审核时间 */
|
/** 审核时间(线下凭证) */
|
||||||
audited_at: string | null
|
audited_at: string | null
|
||||||
auditor_id: number | null
|
auditor_id: number | null
|
||||||
/** 审核备注(拒绝原因) */
|
/** 审核备注(拒绝原因,线下凭证) */
|
||||||
audit_remark: string | null
|
audit_remark: string | null
|
||||||
|
/** 在线支付成功时间(在线支付单非空) */
|
||||||
|
paid_at?: string | null
|
||||||
|
/** 网关交易号(在线支付单非空) */
|
||||||
|
trade_no?: string | null
|
||||||
created_at: string
|
created_at: string
|
||||||
/** 列表返回:合并账单数 */
|
/** 列表返回:合并账单数 */
|
||||||
bills_count?: number
|
bills_count?: number
|
||||||
@@ -98,3 +122,46 @@ export function createPaymentApi(data: {
|
|||||||
export function getPaymentDetailApi(id: number) {
|
export function getPaymentDetailApi(id: number) {
|
||||||
return get<PaymentDetail>(`/mini/payment/${id}`)
|
return get<PaymentDetail>(`/mini/payment/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 在线支付下单返回(pay_params 为旺铺网关透传的 wx.requestPayment 调起参数,以网关实际返回为准) */
|
||||||
|
export interface OnlinePaymentCreateResult {
|
||||||
|
id: number
|
||||||
|
/** 支付单号(ZF 前缀,= 上送网关的商户订单号 mer_order_id),查询/对账用 */
|
||||||
|
payment_no: string
|
||||||
|
/** 应付金额(= 所选账单总额合计,元) */
|
||||||
|
amount: string
|
||||||
|
pay_params: {
|
||||||
|
timeStamp?: string
|
||||||
|
nonceStr?: string
|
||||||
|
package?: string
|
||||||
|
signType?: string
|
||||||
|
paySign?: string
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 在线支付结果查询返回 */
|
||||||
|
export interface OnlinePaymentQueryResult {
|
||||||
|
payment_no: string
|
||||||
|
/** 0 待支付 / 1 支付成功(账单已置已支付)/ 2 支付失败(账单已释放) */
|
||||||
|
status: OnlinePayStatus
|
||||||
|
status_name: string
|
||||||
|
paid_at: string | null
|
||||||
|
trade_no: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发起在线支付(合并账单下单):POST /mini/payment/online
|
||||||
|
* code 为 wx.login() 返回的登录凭证(后端换付款人 openid)
|
||||||
|
*/
|
||||||
|
export function createOnlinePaymentApi(data: { bill_ids: number[]; code: string; remark?: string }) {
|
||||||
|
return post<OnlinePaymentCreateResult>('/mini/payment/online', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主动查询在线支付结果(网关后台通知延迟/丢失时的兜底):GET /mini/payment/online/{payment_no}/query
|
||||||
|
* 网关返回已支付则立即结账(与后台通知同一幂等逻辑)
|
||||||
|
*/
|
||||||
|
export function queryOnlinePaymentApi(paymentNo: string) {
|
||||||
|
return get<OnlinePaymentQueryResult>(`/mini/payment/online/${paymentNo}/query`)
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ const LOGIN_PATH = '/pages/login/index'
|
|||||||
/** 默认请求超时(ms) */
|
/** 默认请求超时(ms) */
|
||||||
const DEFAULT_TIMEOUT = 15000
|
const DEFAULT_TIMEOUT = 15000
|
||||||
/** 接口根地址(uploadFile 等原生请求同样使用) */
|
/** 接口根地址(uploadFile 等原生请求同样使用) */
|
||||||
export const BASE_URL = "http://localhost:8000/index.php"
|
// export const BASE_URL = "http://localhost:8000/index.php"
|
||||||
// export const BASE_URL = "https://purchase.henanklkj.com/index.php"
|
export const BASE_URL = "https://purchase.henanklkj.com/index.php"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTTP 状态码 → 错误提示映射
|
* HTTP 状态码 → 错误提示映射
|
||||||
|
|||||||
Reference in New Issue
Block a user