fix(payment): rely on Huihui callback for status

This commit is contained in:
stefanfeng
2026-10-16 17:00:35 +08:00
parent 85d0c57548
commit ddb8ea9802
7 changed files with 23 additions and 177 deletions
+6 -24
View File
@@ -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)
+1
View File
@@ -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
+9 -84
View File
@@ -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>