From 74aee003e7e79016711650947e50301bd0fc4724 Mon Sep 17 00:00:00 2001
From: liu <2302563948@qq.com>
Date: Fri, 4 Sep 2026 21:54:10 +0800
Subject: [PATCH] =?UTF-8?q?H5=E6=94=AF=E4=BB=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/index.html | 1 +
src/pages/payment/index.tsx | 181 ++++++++++++++++++++++++++++++++++--
src/services/payment.ts | 15 ++-
3 files changed, 187 insertions(+), 10 deletions(-)
diff --git a/src/index.html b/src/index.html
index 49264a4..31a1bc6 100644
--- a/src/index.html
+++ b/src/index.html
@@ -10,6 +10,7 @@
订货采购
+
diff --git a/src/pages/payment/index.tsx b/src/pages/payment/index.tsx
index c2e4342..d8dc94c 100644
--- a/src/pages/payment/index.tsx
+++ b/src/pages/payment/index.tsx
@@ -20,10 +20,68 @@ const MAX_VOUCHERS = 3
/** 在线支付(调起 wx.requestPayment)仅微信小程序支持 */
const IS_WEAPP = process.env.TARO_ENV === 'weapp'
-/** 支付方式选项(在线支付仅小程序端展示,排在最前) */
+/** H5 端处于微信内置浏览器时,可走公众号网页授权 + JSAPI 在线支付 */
+const IS_H5_WECHAT =
+ process.env.TARO_ENV === 'h5' &&
+ typeof navigator !== 'undefined' &&
+ /micromessenger/i.test(navigator.userAgent)
+
+/** 在线支付(旺铺网关 JSAPI)是否可用:小程序 / 微信内 H5 */
+const ONLINE_PAY_AVAILABLE = IS_WEAPP || IS_H5_WECHAT
+
+/** H5 公众号支付草稿存储 key(授权跳转前暂存账单选择,回跳后恢复) */
+const H5_PAY_DRAFT_KEY = 'h5_online_pay_draft'
+
+/** H5 公众号支付草稿(授权回跳页面重载,勾选状态经 sessionStorage 恢复) */
+interface H5PayDraft {
+ bill_ids: number[]
+ remark?: string
+}
+
+/**
+ * 调起公众号 JSAPI 支付(WeixinJSBridge 未注入时等待 WeixinJSBridgeReady 事件)
+ * resolve: 'ok' 支付成功 / 'cancel' 用户取消 / 'fail' 调起失败
+ */
+function invokeWechatJsapiPay(payParams: Record): Promise<'ok' | 'cancel' | 'fail'> {
+ return new Promise(resolve => {
+ const invoke = () => {
+ ;(window as any).WeixinJSBridge.invoke(
+ 'getBrandWCPayRequest',
+ {
+ appId: String(payParams.appId || ''),
+ timeStamp: String(payParams.timeStamp || ''),
+ nonceStr: String(payParams.nonceStr || ''),
+ package: String(payParams.package || ''),
+ signType: String(payParams.signType || 'RSA'),
+ paySign: String(payParams.paySign || ''),
+ },
+ (res: any) => {
+ const msg: string = res?.err_msg || ''
+ if (msg === 'get_brand_wcpay_request:ok') resolve('ok')
+ else if (msg === 'get_brand_wcpay_request:cancel') resolve('cancel')
+ else resolve('fail')
+ },
+ )
+ }
+ if ((window as any).WeixinJSBridge) {
+ invoke()
+ } else {
+ document.addEventListener('WeixinJSBridgeReady', invoke, { once: true })
+ }
+ })
+}
+
+/** 支付方式选项(在线支付仅小程序/微信内 H5 展示,排在最前) */
const PAY_METHODS: Array<{ value: PayMethod; label: string; icon: string; desc: string }> = [
- ...(IS_WEAPP
- ? [{ value: 4 as PayMethod, label: '微信在线支付', icon: 'wechat', desc: '小程序内直接付款,免上传凭证' }]
+ ...(ONLINE_PAY_AVAILABLE
+ ? [
+ {
+ value: 4 as PayMethod,
+ label: '微信在线支付',
+ icon: 'wechat',
+ desc: IS_WEAPP ? '小程序内直接付款,免上传凭证' : '微信内直接付款,免上传凭证',
+ },
+ ]
: []),
{ value: 1, label: '微信支付', icon: 'wechat', desc: '扫码完成转账' },
{ value: 2, label: '支付宝', icon: 'alipay', desc: '扫码完成转账' },
@@ -45,7 +103,7 @@ export default function PaymentPage() {
const loadingRef = useRef(false)
const [config, setConfig] = useState(null)
- const [payMethod, setPayMethod] = useState(IS_WEAPP ? 4 : 1)
+ const [payMethod, setPayMethod] = useState(ONLINE_PAY_AVAILABLE ? 4 : 1)
const [vouchers, setVouchers] = useState([])
const [remark, setRemark] = useState('')
const [uploading, setUploading] = useState(false)
@@ -247,14 +305,123 @@ export default function PaymentPage() {
}
}, [submitting, selectedIds, remark, loadBills])
- /** 提交入口:按支付方式分发 */
+ /**
+ * H5 公众号支付主流程(授权回跳后执行):
+ * 授权 code 下单(scene=mp,后端换付款人 openid)→ WeixinJSBridge 调起支付 → 主动查询同步结果
+ * 与小程序端一致:无论成功/取消都跳转支付详情(待支付单可在详情页刷新同步结果)
+ */
+ const runH5OnlinePay = useCallback(
+ async (billIds: number[], code: string, remarkText?: string) => {
+ setSubmitting(true)
+ try {
+ const res = await createOnlinePaymentApi({
+ bill_ids: billIds,
+ code,
+ scene: 'mp',
+ remark: remarkText,
+ })
+ const { id, payment_no, pay_params } = res.data
+ const result = await invokeWechatJsapiPay(pay_params)
+ if (result !== 'ok') {
+ // 用户取消或调起失败:账单仍锁定在支付单中,进详情页可刷新同步/稍后处理
+ Taro.showToast({
+ title: result === 'cancel' ? '已取消支付' : '支付调起失败,请稍后重试',
+ icon: 'none',
+ })
+ setTimeout(() => {
+ Taro.redirectTo({ url: `/pages/payment-detail/index?id=${id}` })
+ }, 800)
+ return
+ }
+ // 主动查询同步结果(网关后台通知延迟/丢失时的兜底结账)
+ 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)
+ }
+ },
+ [loadBills],
+ )
+
+ /** H5 公众号支付:处理微信授权回跳(URL 携带 code 且本地存在支付草稿时自动继续支付) */
+ const h5CallbackRef = useRef(false)
+ useEffect(() => {
+ if (!IS_H5_WECHAT || h5CallbackRef.current || !loggedIn) return
+ const code = new URLSearchParams(window.location.search).get('code')
+ if (!code) return
+ // 清理地址栏授权参数,避免刷新/分享带出已失效的 code
+ window.history.replaceState(null, '', window.location.pathname)
+ let draft: H5PayDraft | null = null
+ try {
+ draft = JSON.parse(window.sessionStorage.getItem(H5_PAY_DRAFT_KEY) || 'null')
+ window.sessionStorage.removeItem(H5_PAY_DRAFT_KEY)
+ } catch {
+ draft = null
+ }
+ if (!draft || !Array.isArray(draft.bill_ids) || draft.bill_ids.length === 0) return
+ h5CallbackRef.current = true
+ setSelectedIds(draft.bill_ids)
+ if (draft.remark) setRemark(draft.remark)
+ runH5OnlinePay(draft.bill_ids, code, draft.remark)
+ }, [loggedIn, runH5OnlinePay])
+
+ /**
+ * H5 公众号支付入口:暂存支付草稿 → 跳转微信网页授权(snsapi_base 静默授权)
+ * 授权后回跳本页携带 code,由上方 effect 恢复草稿并继续支付
+ */
+ const handleH5OnlinePay = useCallback(async () => {
+ if (submitting) return
+ if (selectedIds.length === 0) {
+ Taro.showToast({ title: '请选择要付款的账单', icon: 'none' })
+ return
+ }
+ setSubmitting(true)
+ try {
+ // mp_appid 可能尚未加载完成,兜底重新拉取
+ let appid = config?.mp_appid
+ if (!appid) {
+ const res = await getPaymentConfigApi()
+ setConfig(res.data)
+ appid = res.data.mp_appid
+ }
+ if (!appid) {
+ Taro.showToast({ title: '公众号支付暂未开通,请选择其他支付方式', icon: 'none' })
+ setSubmitting(false)
+ return
+ }
+ const draft: H5PayDraft = { bill_ids: selectedIds, remark: remark.trim() || undefined }
+ window.sessionStorage.setItem(H5_PAY_DRAFT_KEY, JSON.stringify(draft))
+ const redirectUri = encodeURIComponent(window.location.origin + window.location.pathname)
+ window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base#wechat_redirect`
+ } catch {
+ setSubmitting(false)
+ }
+ }, [submitting, selectedIds, remark, config])
+
+ /** 提交入口:按支付方式与端分发(小程序 wx.requestPayment / H5 公众号 JSAPI / 线下凭证) */
const handleSubmit = useCallback(() => {
if (isOnline) {
- handleOnlinePay()
+ if (IS_H5_WECHAT) {
+ handleH5OnlinePay()
+ } else {
+ handleOnlinePay()
+ }
} else {
handleVoucherSubmit()
}
- }, [isOnline, handleOnlinePay, handleVoucherSubmit])
+ }, [isOnline, handleOnlinePay, handleH5OnlinePay, handleVoucherSubmit])
/** 当前支付方式的收款展示 */
const renderMethodContent = () => {
diff --git a/src/services/payment.ts b/src/services/payment.ts
index f01127a..f9c9357 100644
--- a/src/services/payment.ts
+++ b/src/services/payment.ts
@@ -42,6 +42,8 @@ export interface PaymentConfig {
wechat_qrcode: string
alipay_qrcode: string
bank_info: string
+ /** 公众号 appid(H5 网页授权取 code 拼授权链接用,配置了公众号支付才返回) */
+ mp_appid?: string
}
/** 支付记录(列表行与详情的 payment 字段一致) */
@@ -123,7 +125,7 @@ export function getPaymentDetailApi(id: number) {
return get(`/mini/payment/${id}`)
}
-/** 在线支付下单返回(pay_params 为旺铺网关透传的 wx.requestPayment 调起参数,以网关实际返回为准) */
+/** 在线支付下单返回(pay_params 为旺铺网关透传的调起参数:小程序给 wx.requestPayment,H5 公众号给 getBrandWCPayRequest,以网关实际返回为准) */
export interface OnlinePaymentCreateResult {
id: number
/** 支付单号(ZF 前缀,= 上送网关的商户订单号 mer_order_id),查询/对账用 */
@@ -131,6 +133,7 @@ export interface OnlinePaymentCreateResult {
/** 应付金额(= 所选账单总额合计,元) */
amount: string
pay_params: {
+ appId?: string
timeStamp?: string
nonceStr?: string
package?: string
@@ -152,9 +155,15 @@ export interface OnlinePaymentQueryResult {
/**
* 发起在线支付(合并账单下单):POST /mini/payment/online
- * code 为 wx.login() 返回的登录凭证(后端换付款人 openid)
+ * code:小程序传 wx.login() 登录凭证(不传 scene);
+ * H5 公众号传网页授权回调 code,需带 scene: 'mp'(后端按场景换付款人 openid)
*/
-export function createOnlinePaymentApi(data: { bill_ids: number[]; code: string; remark?: string }) {
+export function createOnlinePaymentApi(data: {
+ bill_ids: number[]
+ code: string
+ scene?: 'mp'
+ remark?: string
+}) {
return post('/mini/payment/online', data)
}