fix(payment): rely on Huihui callback for status
This commit is contained in:
@@ -85,42 +85,6 @@ def update_order_status(order_no: str, body: dict = Body(...)):
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/orders/{order_no}/refund")
|
||||
def request_refund(order_no: str, body: dict = Body(...)):
|
||||
try:
|
||||
data = finance_service.request_refund(
|
||||
order_no,
|
||||
reason=str(body.get("reason") or "").strip(),
|
||||
operator=str(body.get("operator") or "后台管理员").strip(),
|
||||
)
|
||||
return ApiResponse(data=data, message="退款申请已提交")
|
||||
except FinanceServiceError as exc:
|
||||
_raise(exc)
|
||||
|
||||
|
||||
@router.get("/refunds")
|
||||
def refunds(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
status: str = Query(""),
|
||||
):
|
||||
db = _session()
|
||||
try:
|
||||
total, items = finance_service.list_refunds(db, page=page, page_size=page_size, status=status)
|
||||
return ApiResponse(data={"total": total, "page": page, "page_size": page_size, "items": items})
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@router.post("/refunds/{refund_no}/confirm")
|
||||
def confirm_refund(refund_no: str, body: dict = Body(...)):
|
||||
try:
|
||||
data = finance_service.confirm_refund(refund_no, body)
|
||||
return ApiResponse(data=data, message="退款结果已登记")
|
||||
except FinanceServiceError as exc:
|
||||
_raise(exc)
|
||||
|
||||
|
||||
@router.get("/invoices")
|
||||
def invoices(
|
||||
page: int = Query(1, ge=1),
|
||||
|
||||
@@ -419,7 +419,6 @@ def charge_via_huihui_org(payload: dict = Body(...), authorization: str = Header
|
||||
scene = str(payload.get("payScene") or "APP").upper()
|
||||
if scene not in PAYMENT_SCENES:
|
||||
return fail("支付场景不正确", 400)
|
||||
pay_type = 6 if scene == "LITE" else (1 if method == "wechat" else 2)
|
||||
if scene == "LITE" and method != "wechat":
|
||||
return fail("微信小程序仅支持微信支付", 400)
|
||||
cents = _price_cents(plan.price)
|
||||
@@ -427,7 +426,7 @@ def charge_via_huihui_org(payload: dict = Body(...), authorization: str = Header
|
||||
result = HuihuiOrgPaymentClient().create_order(
|
||||
token=user.huihui_token, user_id=user.huihui_user_id,
|
||||
name=user.nickname, phone=user.phone, avatar=user.avatar_url,
|
||||
amount=f"{cents / 100:.2f}", pay_type=pay_type,
|
||||
amount=f"{cents / 100:.2f}",
|
||||
)
|
||||
except HuihuiPaymentError as exc:
|
||||
return fail(str(exc), 502)
|
||||
@@ -445,7 +444,10 @@ def charge_via_huihui_org(payload: dict = Body(...), authorization: str = Header
|
||||
)
|
||||
db.add(order)
|
||||
db.commit()
|
||||
return ok(_payment_payload(order, get_or_create_account(db, user.id)))
|
||||
result = _payment_payload(order, get_or_create_account(db, user.id))
|
||||
if "fat-open.99hui.com" in os.getenv("HUIHUI_ORG_BASE_URL", ""):
|
||||
result["checkoutToken"] = user.huihui_token
|
||||
return ok(result)
|
||||
|
||||
|
||||
@router.get("/token/payment/{order_id}")
|
||||
@@ -457,27 +459,7 @@ def payment_status(order_id: str, authorization: str = Header(None), db: Session
|
||||
).first()
|
||||
if not order:
|
||||
return fail("支付订单不存在", 404)
|
||||
if order.provider == "huihui_org" and order.status == "pending":
|
||||
try:
|
||||
details = HuihuiOrgPaymentClient().get_order(
|
||||
token=user.huihui_token, user_id=user.huihui_user_id,
|
||||
order_no=order.provider_order_no,
|
||||
)
|
||||
status = int(details.get("payStatus") or 0)
|
||||
if (str(details.get("orderNo") or "") == order.provider_order_no
|
||||
and str(details.get("userId") or "") == user.huihui_user_id
|
||||
and int(details.get("chargeType") or 0) == 9
|
||||
and _callback_amount_cents({"chargeAmount": details.get("chargeAmount")}) == order.price_cents):
|
||||
order.provider_status = str(status)
|
||||
if status in (2, 3, 4):
|
||||
_settle_paid_order(db, order, provider_status=f"ORG_{status}",
|
||||
transaction_no=order.provider_order_no)
|
||||
elif status == 5:
|
||||
order.status = "closed"
|
||||
db.commit()
|
||||
db.refresh(order)
|
||||
except (HuihuiPaymentError, TypeError, ValueError):
|
||||
pass # 查询失败时保持待支付,不将未经核对的结果入账。
|
||||
# 会会组织支付以回调为准;查询此接口只返回本地订单,不向会会轮询查单。
|
||||
if order.provider == "wechat_virtual" and order.status == "pending" and user.wechat_mp_openid:
|
||||
try:
|
||||
provider_data = query_wechat_virtual_order(
|
||||
|
||||
@@ -36,8 +36,8 @@ class HuihuiOrgPaymentClient(HuihuiPaymentClient):
|
||||
return data
|
||||
|
||||
def create_order(self, *, token: str, user_id: str, name: str, phone: str,
|
||||
avatar: str, amount: str, pay_type: int) -> dict:
|
||||
body = {"chargeAmount": float(amount), "payType": pay_type,
|
||||
avatar: str, amount: str) -> dict:
|
||||
body = {"chargeAmount": float(amount),
|
||||
"commodityName": "数字分身积分充值", "userId": user_id,
|
||||
"name": name or "", "phoneNumber": phone or "", "avatar": avatar or ""}
|
||||
return self._request("POST", "/order/tokenRecharge", token=token, user_id=user_id, body=body)
|
||||
|
||||
@@ -158,6 +158,7 @@ export interface TokenPaymentOrder {
|
||||
status: 'pending' | 'paid' | 'failed' | 'closed' | 'refunded'
|
||||
provider: 'huihui' | 'wechat_virtual' | 'huihui_org'
|
||||
providerOrderNo?: string
|
||||
checkoutToken?: string
|
||||
providerStatus: string
|
||||
payMessage: string
|
||||
failureReason: string
|
||||
|
||||
@@ -45,8 +45,8 @@
|
||||
<section class="checkout-section">
|
||||
<button
|
||||
class="checkout-btn"
|
||||
:class="{ disabled: !selectedPlan || charging || Boolean(pendingOrderId) }"
|
||||
:disabled="!selectedPlan || charging || Boolean(pendingOrderId)"
|
||||
:class="{ disabled: !selectedPlan || charging }"
|
||||
:disabled="!selectedPlan || charging"
|
||||
@click="doCharge"
|
||||
>
|
||||
{{ checkoutLabel }}
|
||||
@@ -85,7 +85,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import {
|
||||
chargeTokenOrg,
|
||||
@@ -93,10 +93,9 @@ import {
|
||||
getRechargePlans,
|
||||
getTokenBalance,
|
||||
getTokenOrders,
|
||||
getTokenPaymentStatus,
|
||||
type TokenPaymentOrder
|
||||
} from '@/api'
|
||||
import { isInUniWebView, onNativeMessage } from '@/utils/uniapp-bridge'
|
||||
import { isInUniWebView } from '@/utils/uniapp-bridge'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -119,7 +118,6 @@ const selectedPlan = ref<any>(null)
|
||||
// 支付方式
|
||||
const paymentNotice = ref('')
|
||||
const paymentNoticeTone = ref<'pending' | 'success' | 'error'>('pending')
|
||||
const pendingOrderId = ref(sessionStorage.getItem('hh_pending_payment_order') || '')
|
||||
const recentOrders = ref<Array<TokenPaymentOrder & { invoice?: any }>>([])
|
||||
const invoiceOrder = ref<(TokenPaymentOrder & { invoice?: any }) | null>(null)
|
||||
const invoiceType = ref<'personal' | 'company'>('personal')
|
||||
@@ -128,9 +126,6 @@ const invoiceTaxNumber = ref('')
|
||||
const invoiceEmail = ref('')
|
||||
const invoiceError = ref('')
|
||||
const invoiceSubmitting = ref(false)
|
||||
let pollTimer: number | undefined
|
||||
let pollDeadline = 0
|
||||
let removeNativeListener: (() => void) | undefined
|
||||
|
||||
// 从后端加载余额与套餐
|
||||
const loadData = async () => {
|
||||
@@ -205,7 +200,6 @@ const formatPointsCopy = (value: string) => value.replace(/\bToken\b/gi, '积分
|
||||
const checkoutLabel = computed(() => {
|
||||
if (!selectedPlan.value) return '请选择充值套餐'
|
||||
if (charging.value) return '正在创建支付订单…'
|
||||
if (pendingOrderId.value) return '等待支付结果…'
|
||||
return '去支付'
|
||||
})
|
||||
|
||||
@@ -216,50 +210,6 @@ const payScene = () => {
|
||||
return 'H5' as const
|
||||
}
|
||||
|
||||
const clearPendingOrder = () => {
|
||||
pendingOrderId.value = ''
|
||||
sessionStorage.removeItem('hh_pending_payment_order')
|
||||
if (pollTimer) window.clearTimeout(pollTimer)
|
||||
pollTimer = undefined
|
||||
}
|
||||
|
||||
const pollPayment = async () => {
|
||||
if (!pendingOrderId.value) return
|
||||
try {
|
||||
const order = await getTokenPaymentStatus(pendingOrderId.value)
|
||||
if (order.status === 'paid') {
|
||||
currentBalance.value = order.balance
|
||||
paymentNoticeTone.value = 'success'
|
||||
paymentNotice.value = `支付成功,${order.pointsAmount.toLocaleString()} 积分已到账`
|
||||
clearPendingOrder()
|
||||
void loadData()
|
||||
return
|
||||
}
|
||||
if (order.status === 'failed' || order.status === 'closed') {
|
||||
paymentNoticeTone.value = 'error'
|
||||
paymentNotice.value = order.status === 'closed' ? '订单已关闭,请重新发起支付' : (order.failureReason || '支付未完成,请重新发起')
|
||||
clearPendingOrder()
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询支付结果失败', error)
|
||||
}
|
||||
if (Date.now() < pollDeadline) pollTimer = window.setTimeout(pollPayment, 2000)
|
||||
else {
|
||||
paymentNoticeTone.value = 'pending'
|
||||
paymentNotice.value = '支付结果仍在确认中,可稍后刷新余额或重新发起'
|
||||
clearPendingOrder()
|
||||
}
|
||||
}
|
||||
|
||||
const startPolling = (orderId: string) => {
|
||||
pendingOrderId.value = orderId
|
||||
sessionStorage.setItem('hh_pending_payment_order', orderId)
|
||||
pollDeadline = Date.now() + 2 * 60 * 1000
|
||||
if (pollTimer) window.clearTimeout(pollTimer)
|
||||
void pollPayment()
|
||||
}
|
||||
|
||||
const doCharge = async () => {
|
||||
if (!selectedPlan.value || charging.value) return
|
||||
charging.value = true
|
||||
@@ -269,18 +219,19 @@ const doCharge = async () => {
|
||||
if (order.status === 'failed') throw new Error(order.failureReason || '支付订单创建失败')
|
||||
if (!order.providerOrderNo || !/^\d+$/.test(order.providerOrderNo)) throw new Error('会会充值订单号缺失')
|
||||
paymentNoticeTone.value = 'pending'
|
||||
paymentNotice.value = '支付订单已创建,正在等待付款结果'
|
||||
startPolling(order.id)
|
||||
paymentNotice.value = '支付订单已创建,支付结果将由会会回调确认'
|
||||
const checkoutBase = window.location.hostname === 'digital.99hui.com'
|
||||
? 'https://99hui.com/huihui-h5/'
|
||||
: 'https://fat-open.99hui.com/huihui-h5/'
|
||||
const evn = sessionStorage.getItem('hh_launch_evn') || 'wx'
|
||||
const evnPart = `&evn=${encodeURIComponent(evn)}`
|
||||
window.location.assign(`${checkoutBase}#/payOrder?orderNo=${encodeURIComponent(order.providerOrderNo)}&isOrg=true${evnPart}`)
|
||||
const tokenPart = order.checkoutToken
|
||||
? `&token=${encodeURIComponent(order.checkoutToken)}`
|
||||
: ''
|
||||
window.location.assign(`${checkoutBase}#/payOrder?orderNo=${encodeURIComponent(order.providerOrderNo)}&isOrg=true${evnPart}${tokenPart}`)
|
||||
} catch (error: any) {
|
||||
paymentNoticeTone.value = 'error'
|
||||
paymentNotice.value = error?.message || '充值失败,请重试'
|
||||
clearPendingOrder()
|
||||
} finally {
|
||||
charging.value = false
|
||||
}
|
||||
@@ -293,32 +244,6 @@ const goBack = () => {
|
||||
|
||||
onMounted(() => {
|
||||
void loadData()
|
||||
removeNativeListener = onNativeMessage((message) => {
|
||||
if (message?.type !== 'paymentResult') return
|
||||
if (message.orderId && message.orderId !== pendingOrderId.value) return
|
||||
if (['cancelled', 'canceled'].includes(String(message.status).toLowerCase())) {
|
||||
paymentNoticeTone.value = 'error'
|
||||
paymentNotice.value = '支付已取消'
|
||||
clearPendingOrder()
|
||||
return
|
||||
}
|
||||
if (String(message.status).toLowerCase() === 'failed') {
|
||||
paymentNoticeTone.value = 'error'
|
||||
paymentNotice.value = message.message || '支付未完成,请重新发起'
|
||||
clearPendingOrder()
|
||||
return
|
||||
}
|
||||
void pollPayment()
|
||||
})
|
||||
if (pendingOrderId.value) {
|
||||
paymentNotice.value = '正在确认上一笔支付结果'
|
||||
startPolling(pendingOrderId.value)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (pollTimer) window.clearTimeout(pollTimer)
|
||||
removeNativeListener?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -100,9 +100,6 @@ export const getFinancePlans = () => request.get('/finance/plans')
|
||||
export const updateFinancePlan = (id, data) => request.patch(`/finance/plans/${id}`, data)
|
||||
export const getFinanceOrders = (params) => request.get('/finance/orders', { params })
|
||||
export const updateFinanceOrderStatus = (orderNo, data) => request.patch(`/finance/orders/${orderNo}/status`, data)
|
||||
export const requestFinanceRefund = (orderNo, data) => request.post(`/finance/orders/${orderNo}/refund`, data)
|
||||
export const getFinanceRefunds = (params) => request.get('/finance/refunds', { params })
|
||||
export const confirmFinanceRefund = (refundNo, data) => request.post(`/finance/refunds/${refundNo}/confirm`, data)
|
||||
export const getFinanceInvoices = (params) => request.get('/finance/invoices', { params })
|
||||
export const updateFinanceInvoice = (invoiceId, data) => request.patch(`/finance/invoices/${invoiceId}`, data)
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
<div class="summary-grid">
|
||||
<div class="stat-card"><div class="stat-label">实收金额</div><div class="stat-value">¥{{ money(summary.paid_revenue) }}</div><small>{{ summary.paid_orders }} 笔已支付</small></div>
|
||||
<div class="stat-card"><div class="stat-label">待支付订单</div><div class="stat-value warning">{{ summary.pending_orders }}</div><small>可关闭或标记失败</small></div>
|
||||
<div class="stat-card"><div class="stat-label">处理中退款</div><div class="stat-value danger">{{ summary.processing_refunds }}</div><small>等待支付渠道确认</small></div>
|
||||
<div class="stat-card"><div class="stat-label">待开发票</div><div class="stat-value success">{{ summary.pending_invoices }}</div><small>等待财务处理</small></div>
|
||||
</div>
|
||||
|
||||
@@ -49,33 +48,16 @@
|
||||
<el-table-column label="下单时间" min-width="165"><template #default="{ row }">{{ dateTime(row.created_at) }}</template></el-table-column>
|
||||
<el-table-column label="操作" width="165" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.status === 'paid' && ['none','',null].includes(row.refund_status)" link type="danger" @click="refund(row)">退款</el-button>
|
||||
<el-dropdown v-if="row.status === 'pending'" @command="command => closeOrder(row, command)">
|
||||
<el-button link type="warning">状态处理<el-icon><ArrowDown /></el-icon></el-button>
|
||||
<template #dropdown><el-dropdown-menu><el-dropdown-item command="closed">关闭订单</el-dropdown-item><el-dropdown-item command="failed">标记失败</el-dropdown-item></el-dropdown-menu></template>
|
||||
</el-dropdown>
|
||||
<span v-if="row.status !== 'pending' && !(row.status === 'paid' && ['none','',null].includes(row.refund_status))" class="muted">已处理</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination v-model:current-page="orderPage" :page-size="20" :total="orderTotal" layout="total, prev, pager, next" @change="loadOrders" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="退款管理" name="refunds">
|
||||
<el-table :data="refunds" v-loading="loading" class="data-table">
|
||||
<el-table-column label="退款单号" min-width="220" prop="refund_no" />
|
||||
<el-table-column label="原订单" min-width="210" prop="order_no" />
|
||||
<el-table-column label="用户" width="150"><template #default="{ row }">{{ row.user_nickname || row.user_phone || '--' }}</template></el-table-column>
|
||||
<el-table-column label="金额" width="100"><template #default="{ row }">¥{{ money(row.amount) }}</template></el-table-column>
|
||||
<el-table-column label="状态" width="110"><template #default="{ row }"><el-tag :type="tagType(row.status)">{{ statusLabel[row.status] || row.status }}</el-tag></template></el-table-column>
|
||||
<el-table-column label="原因" min-width="160" prop="reason" show-overflow-tooltip />
|
||||
<el-table-column label="创建时间" min-width="165"><template #default="{ row }">{{ dateTime(row.created_at) }}</template></el-table-column>
|
||||
<el-table-column label="对账确认" width="155" fixed="right">
|
||||
<template #default="{ row }"><template v-if="['pending','processing'].includes(row.status)"><el-button link type="success" @click="confirmRefund(row, 'succeeded')">已退款</el-button><el-button link type="danger" @click="confirmRefund(row, 'failed')">失败</el-button></template><span v-else class="muted">{{ row.failure_reason || '已完成' }}</span></template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="发票管理" name="invoices">
|
||||
<el-table :data="invoices" v-loading="loading" class="data-table">
|
||||
<el-table-column label="订单号" min-width="210" prop="order_no" />
|
||||
@@ -113,13 +95,13 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { confirmFinanceRefund, getFinanceInvoices, getFinanceOrders, getFinancePlans, getFinanceRefunds, getFinanceSummary, requestFinanceRefund, updateFinanceInvoice, updateFinanceOrderStatus, updateFinancePlan } from '@/api'
|
||||
import { getFinanceInvoices, getFinanceOrders, getFinancePlans, getFinanceSummary, updateFinanceInvoice, updateFinanceOrderStatus, updateFinancePlan } from '@/api'
|
||||
|
||||
const activeTab = ref('orders')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const summary = ref({ paid_revenue: 0, paid_orders: 0, pending_orders: 0, processing_refunds: 0, pending_invoices: 0 })
|
||||
const orders = ref([]), refunds = ref([]), invoices = ref([])
|
||||
const summary = ref({ paid_revenue: 0, paid_orders: 0, pending_orders: 0, pending_invoices: 0 })
|
||||
const orders = ref([]), invoices = ref([])
|
||||
const plans = ref([]), planDialog = ref(false), currentPlan = ref(null)
|
||||
const planForm = ref({ name: '', amount: 1, price: 0, badge: '', desc: '' })
|
||||
const orderPage = ref(1), orderTotal = ref(0)
|
||||
@@ -137,18 +119,13 @@ const tagType = value => ({ paid: 'success', succeeded: 'success', issued: 'succ
|
||||
async function loadSummary() { const res = await getFinanceSummary(); summary.value = res.data || summary.value }
|
||||
async function loadPlans() { const res = await getFinancePlans(); plans.value = res.data || [] }
|
||||
async function loadOrders() { const res = await getFinanceOrders({ page: orderPage.value, page_size: 20, ...filters.value }); orders.value = res.data?.items || []; orderTotal.value = res.data?.total || 0 }
|
||||
async function loadRefunds() { const res = await getFinanceRefunds({ page: 1, page_size: 100 }); refunds.value = res.data?.items || [] }
|
||||
async function loadInvoices() { const res = await getFinanceInvoices({ page: 1, page_size: 100 }); invoices.value = res.data?.items || [] }
|
||||
async function loadActive() { loading.value = true; try { if (activeTab.value === 'plans') await loadPlans(); if (activeTab.value === 'orders') await loadOrders(); if (activeTab.value === 'refunds') await loadRefunds(); if (activeTab.value === 'invoices') await loadInvoices() } finally { loading.value = false } }
|
||||
async function loadAll() { loading.value = true; try { await Promise.all([loadSummary(), loadPlans(), loadOrders(), loadRefunds(), loadInvoices()]) } finally { loading.value = false } }
|
||||
async function loadActive() { loading.value = true; try { if (activeTab.value === 'plans') await loadPlans(); if (activeTab.value === 'orders') await loadOrders(); if (activeTab.value === 'invoices') await loadInvoices() } finally { loading.value = false } }
|
||||
async function loadAll() { loading.value = true; try { await Promise.all([loadSummary(), loadPlans(), loadOrders(), loadInvoices()]) } finally { loading.value = false } }
|
||||
function editPlan(row) { currentPlan.value = row; planForm.value = { name: row.name, amount: Number(row.amount), price: Number(row.price), badge: row.badge || '', desc: row.desc || '' }; planDialog.value = true }
|
||||
async function savePlan() { if (!currentPlan.value) return; saving.value = true; try { await updateFinancePlan(currentPlan.value.id, planForm.value); ElMessage.success('充值套餐已更新'); planDialog.value = false; await loadPlans() } finally { saving.value = false } }
|
||||
|
||||
async function refund(row) {
|
||||
try { const { value } = await ElMessageBox.prompt('退款成功后会扣回本订单发放的全部积分。', `退款 ¥${money(row.price)}`, { inputPlaceholder: '请输入退款原因', inputValidator: value => value?.trim() ? true : '退款原因不能为空', confirmButtonText: '提交退款', cancelButtonText: '取消' }); await requestFinanceRefund(row.order_no, { reason: value, operator: '后台管理员' }); ElMessage.success('退款已提交渠道处理'); await loadAll() } catch (error) { if (error !== 'cancel' && error !== 'close') console.error(error) }
|
||||
}
|
||||
async function closeOrder(row, status) { try { await ElMessageBox.confirm(`确认将订单标记为“${status === 'closed' ? '已关闭' : '失败'}”?`, '订单状态处理', { type: 'warning' }); await updateFinanceOrderStatus(row.order_no, { status, reason: '后台管理员处理' }); ElMessage.success('订单状态已更新'); await loadAll() } catch {} }
|
||||
async function confirmRefund(row, status) { try { const message = status === 'succeeded' ? '仅在支付渠道后台已确认退款到账时操作。' : '确认供应商退款失败?'; await ElMessageBox.confirm(message, '退款对账确认', { type: 'warning' }); await confirmFinanceRefund(row.refund_no, { status, failureReason: status === 'failed' ? '供应商退款失败' : '' }); ElMessage.success('退款结果已登记'); await loadAll() } catch {} }
|
||||
function openInvoice(row) { currentInvoice.value = row; invoiceForm.value = { invoiceNo: '', invoiceUrl: '', remark: '' }; invoiceDialog.value = true }
|
||||
async function issueInvoice() { if (!invoiceForm.value.invoiceNo.trim()) return ElMessage.warning('请填写发票号码'); saving.value = true; try { await updateFinanceInvoice(currentInvoice.value.id, { status: 'issued', ...invoiceForm.value }); ElMessage.success('发票已登记'); invoiceDialog.value = false; await loadAll() } finally { saving.value = false } }
|
||||
async function rejectInvoice(row) { try { const { value } = await ElMessageBox.prompt('请输入驳回原因', '驳回发票申请', { inputValidator: value => value?.trim() ? true : '驳回原因不能为空' }); await updateFinanceInvoice(row.id, { status: 'rejected', remark: value }); ElMessage.success('发票申请已驳回'); await loadAll() } catch {} }
|
||||
|
||||
Reference in New Issue
Block a user