feat: 收银台支持支付宝支付(微信/支付宝选择器)

PC 收银台此前硬编码「请使用微信扫码支付」,只能走微信 Native。

改动:
- 支付方式选择器(微信支付 / 支付宝),切换即重新发起支付并清掉上一张码——
  同一订单号换支付类型重复下单会被渠道拒(微信回 OUT_TRADE_NO_USED),
  两种支付信息同时留在页面上只会让用户困惑。
- 支付宝走电脑网站支付:后端返回自提交表单,用 document.write 写进新窗口。
  刻意不用 v-html——它插入的脚本标签不会执行(HTML 规范如此),
  表单永远不会自动提交。原收银台页留在原标签页继续轮询查单。
- 弹窗被拦截时给出可行动提示,并保留「前往支付宝付款」按钮兜底。
- 回到一张曾用支付宝发起过的待支付订单时,默认选中支付宝,
  避免又拿微信去撞一次「订单号已使用」。
- useHjcPayStatus 增加 channel 参数,仅供提示文案说对「微信侧/支付宝侧」。
  **状态词表不变**——后端已把两个渠道的状态映射成渠道无关的 PaymentStatus,
  前端再加一份支付宝词表等于把渠道知识泄漏进 UI。顺带把这层理由写进注释。
- 修正代码注释里对 ADR 0009 的引用:工作区 docs/adr 下并没有这一篇,
  相关决策现记录在 ADR 0008。

验证:eslint 0 error;npm run build:dev 构建通过。
This commit is contained in:
2026-09-20 19:18:02 +08:00
parent 0d6410c17f
commit 31f2f07c5c
4 changed files with 232 additions and 110 deletions
+28 -12
View File
@@ -1,25 +1,33 @@
/** /**
* 支付状态机(hjc-web 购标支付步骤用)。 * 支付状态机(hjc-web 购标支付步骤与收银台共用)。
* *
* 唯一事实来源是**微信侧**:轮询 `GET /api/tender/pay-status`,由后端向微信查单后给出结论。 * 唯一事实来源是**支付渠道侧**:轮询 `GET /api/tender/pay-status`,
* 不要退回 `mark-paid`——那个接口不查单,用户点一下就能把订单置为已支付(见 ADR 0009)。 * 由后端向渠道查单后给出结论。不要退回 `mark-paid`——那个接口不查单,
* 用户点一下就能把订单置为已支付(见 ADR 0008)。
*
* **渠道无关**:下面两个状态集合用的是后端 `PaymentStatus` 的枚举名。微信的
* `trade_state` 与支付宝的 `trade_status` 都已由各自的支付策略映射到该枚举
* (`WechatJsapiStrategy#convertWechatPaymentStatus` / `AlipayStrategy#convertAlipayPaymentStatus`)。
* 因此**不要**在这里加支付宝的状态词表——那等于把渠道知识泄漏进 UI,
* 且下次接入新渠道还要再改一遍。
*/ */
/** /**
* 微信 Native 支付码的有效期。 * 支付码的有效期。
* *
* 微信侧默认 2 小时,且**服务端不下发过期时间**(`PaymentResponse.wechatNative()` 不设 `expireTime`), * 微信 Native 默认 2 小时;支付宝当面付的超时由下单时的 `timeout_express` 决定。
* 服务端都不下发过期时刻(`PaymentResponse` 不设 `expireTime`),
* 所以只能以「本次拿到支付码的时刻」本地计时;到点停止轮询并提示重新获取。 * 所以只能以「本次拿到支付码的时刻」本地计时;到点停止轮询并提示重新获取。
*/ */
export const HJC_PAY_QR_TTL_MS = 2 * 60 * 60 * 1000 export const HJC_PAY_QR_TTL_MS = 2 * 60 * 60 * 1000
/** 轮询间隔:查单是以微信为准的代价,取到「扫完到页面反应」不至于让人以为卡住 */ /** 轮询间隔:查单是以渠道为准的代价,取到「扫完到页面反应」不至于让人以为卡住 */
const POLL_INTERVAL_MS = 3000 const POLL_INTERVAL_MS = 3000
/** 微信侧终态:不用再付了 */ /** 渠道侧终态:不用再付了 */
const SETTLED_STATUS = new Set(['SUCCESS', 'REFUNDED']) const SETTLED_STATUS = new Set(['SUCCESS', 'REFUNDED'])
/** 微信侧终态(坏的那种):这单在微信那边已经付不了了,继续轮询没有意义 */ /** 渠道侧终态(坏的那种):这单在渠道那边已经付不了了,继续轮询没有意义 */
const DEAD_STATUS = new Set(['FAILED', 'CANCELLED', 'TIMEOUT', 'REFUND_FAILED']) const DEAD_STATUS = new Set(['FAILED', 'CANCELLED', 'TIMEOUT', 'REFUND_FAILED'])
export interface HjcPayQueryResult { export interface HjcPayQueryResult {
@@ -35,8 +43,13 @@ export interface HjcPayQueryResult {
/** /**
* @param redirect 401 跳登录页时带上,便于登录后回跳 * @param redirect 401 跳登录页时带上,便于登录后回跳
* @param channel 支付渠道(`'wechat'` / `'alipay'`),只用于把提示文案里的
* 「微信侧 / 支付宝侧」说对。**状态词表不因此变化**——见文件头注释。
*/ */
export function useHjcPayStatus(redirect: () => string) { export function useHjcPayStatus(
redirect: () => string,
channel: () => 'wechat' | 'alipay' = () => 'wechat'
) {
const { handleAuthCode } = useHjcAuth() const { handleAuthCode } = useHjcAuth()
/** /**
* 与 `orders.vue` 同理:服务端渲染期间发出的站内请求必须走 `useRequestFetch()`, * 与 `orders.vue` 同理:服务端渲染期间发出的站内请求必须走 `useRequestFetch()`,
@@ -45,6 +58,9 @@ export function useHjcPayStatus(redirect: () => string) {
*/ */
const requestFetch = useRequestFetch() const requestFetch = useRequestFetch()
/** 提示文案里的渠道名;只影响措辞,不影响任何判定 */
const channelLabel = computed(() => (channel() === 'alipay' ? '支付宝侧' : '微信侧'))
const status = ref<string | null>(null) const status = ref<string | null>(null)
const paid = ref(false) const paid = ref(false)
const querying = ref(false) const querying = ref(false)
@@ -97,9 +113,9 @@ export function useHjcPayStatus(redirect: () => string) {
paid.value = true paid.value = true
stop() stop()
} else if (name && DEAD_STATUS.has(name)) { } else if (name && DEAD_STATUS.has(name)) {
// 微信侧已终态且不是成功:这张支付码已经没用了,继续轮询只是空转 // 渠道侧已终态且不是成功:这张支付码已经没用了,继续轮询只是空转
stop() stop()
error.value = '该支付单在微信侧已关闭,请重新获取二维码' error.value = `该支付单在${channelLabel.value}已关闭,请重新获取二维码`
} }
return { handled: false, status: name, settled: paid.value } return { handled: false, status: name, settled: paid.value }
} catch (e: any) { } catch (e: any) {
@@ -165,5 +181,5 @@ export function useHjcPayStatus(redirect: () => string) {
document.removeEventListener('visibilitychange', onVisibilityChange) document.removeEventListener('visibilitychange', onVisibilityChange)
}) })
return { status, paid, querying, error, expired, start, stop, query } return { status, paid, querying, error, expired, channelLabel, start, stop, query }
} }
+124 -22
View File
@@ -35,27 +35,68 @@
<!-- 待支付:进入即自动发起支付(与购标流程的支付步骤、h5 收银台一致) --> <!-- 待支付:进入即自动发起支付(与购标流程的支付步骤、h5 收银台一致) -->
<div v-else class="mx-auto mt-6 max-w-2xl rounded-lg border border-gray-200 p-8 text-center"> <div v-else class="mx-auto mt-6 max-w-2xl rounded-lg border border-gray-200 p-8 text-center">
<h2 class="text-xl font-bold text-gray-900">请使用微信扫码支付</h2> <h2 class="text-xl font-bold text-gray-900">请使用{{ channelLabel }}支付</h2>
<p class="mt-2 text-sm text-gray-500">订单号:{{ orderNo }}</p> <p class="mt-2 text-sm text-gray-500">订单号:{{ orderNo }}</p>
<p class="mt-1 text-sm text-gray-500">{{ order?.projectName }}</p> <p class="mt-1 text-sm text-gray-500">{{ order?.projectName }}</p>
<!-- 金额一律来自**订单本身**(by-no),不来自支付响应: <!-- 金额一律来自**订单本身**(by-no),不来自支付响应:
支付一失败页面就会停在 0.00,而收银台恰恰是最需要看到金额的地方 --> 支付一失败页面就会停在 0.00,而收银台恰恰是最需要看到金额的地方 -->
<p class="my-4 text-2xl font-bold text-blue-600">¥{{ formatMoney(amount) }}</p> <p class="my-4 text-2xl font-bold text-blue-600">¥{{ formatMoney(amount) }}</p>
<template v-if="codeUrl"> <!--
支付方式选择。**切换方式即重新发起支付**——同一订单号不能换支付类型重复下单
(微信会回 OUT_TRADE_NO_USED),所以切换前把上一张码清掉,避免两张码同时留在页面上。
-->
<div class="mb-6 inline-flex rounded-md border border-gray-200 p-1">
<button
v-for="opt in payOptions"
:key="opt.value"
type="button"
class="rounded px-4 py-1.5 text-sm font-medium transition-colors disabled:opacity-50"
:class="payType === opt.value
? 'bg-blue-600 text-white'
: 'text-gray-600 hover:bg-gray-50'"
:disabled="starting || refreshing"
@click="switchPayType(opt.value)"
>
{{ opt.label }}
</button>
</div>
<!-- 支付宝网页支付:后端返回一段自提交表单,写进新页面即跳转账单 -->
<template v-if="alipayForm">
<div class="rounded-md bg-blue-50 px-4 py-6 text-sm text-blue-700">
已为你打开支付宝收银台。若新页面没有自动打开,请点击下方按钮。
</div>
<button
class="mt-6 w-full rounded-md bg-blue-600 py-3 font-medium text-white hover:bg-blue-700 disabled:opacity-50"
:disabled="confirming"
@click="openAlipayForm"
>
{{ confirming ? '确认中...' : '前往支付宝付款' }}
</button>
<button
class="mt-3 w-full rounded-md border border-gray-300 py-3 font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
:disabled="confirming"
@click="confirmPaid"
>
{{ confirming ? '确认中...' : '我已完成支付,确认订单' }}
</button>
</template>
<template v-else-if="codeUrl">
<!-- 过期只是置灰提示,仍留在页面上:万一用户是刚过点就扫的,还能看见自己扫的是哪张码 --> <!-- 过期只是置灰提示,仍留在页面上:万一用户是刚过点就扫的,还能看见自己扫的是哪张码 -->
<div :class="payExpired ? 'opacity-30' : ''"> <div :class="payExpired ? 'opacity-30' : ''">
<HjcPayQrcode :value="codeUrl" :size="240" /> <HjcPayQrcode :value="codeUrl" :size="240" :alt="qrAlt" />
</div> </div>
<p class="mt-3 text-sm text-gray-500">请在 2 小时内用微信扫描上方二维码完成支付</p> <p class="mt-3 text-sm text-gray-500">请在 2 小时内用{{ channelLabel }}扫描上方二维码完成支付</p>
<p v-if="payExpired" class="mt-2 text-sm text-amber-600">二维码已过期,请重新获取后再扫码</p> <p v-if="payExpired" class="mt-2 text-sm text-amber-600">二维码已过期,请重新获取后再扫码</p>
</template> </template>
<div v-else class="rounded-md bg-gray-50 px-4 py-6 text-sm text-gray-500"> <div v-else class="rounded-md bg-gray-50 px-4 py-6 text-sm text-gray-500">
{{ starting ? '正在获取支付二维码...' : '暂未获取到支付二维码,请点击下方按钮重新获取' }} {{ starting ? `正在获取${channelLabel}支付` : '暂未获取到支付信息,请点击下方按钮重新获取' }}
</div> </div>
<button <button
v-if="codeUrl" v-if="codeUrl && !alipayForm"
class="mt-6 w-full rounded-md bg-blue-600 py-3 font-medium text-white hover:bg-blue-700 disabled:opacity-50" class="mt-6 w-full rounded-md bg-blue-600 py-3 font-medium text-white hover:bg-blue-700 disabled:opacity-50"
:disabled="confirming" :disabled="confirming"
@click="confirmPaid" @click="confirmPaid"
@@ -63,6 +104,7 @@
{{ confirming ? '确认中...' : '我已完成支付,确认订单' }} {{ confirming ? '确认中...' : '我已完成支付,确认订单' }}
</button> </button>
<button <button
v-if="!alipayForm"
class="mt-6 w-full rounded-md py-3 font-medium disabled:opacity-50" class="mt-6 w-full rounded-md py-3 font-medium disabled:opacity-50"
:class="codeUrl :class="codeUrl
? 'border border-gray-300 text-gray-700 hover:bg-gray-50' ? 'border border-gray-300 text-gray-700 hover:bg-gray-50'
@@ -70,7 +112,7 @@
:disabled="refreshing || starting" :disabled="refreshing || starting"
@click="refreshQr" @click="refreshQr"
> >
{{ (refreshing || starting) ? '获取中...' : (codeUrl ? '重新获取二维码' : '获取支付二维码') }} {{ (refreshing || starting) ? '获取中...' : (codeUrl ? '重新获取二维码' : '获取支付信息') }}
</button> </button>
<p v-if="error || payError" class="mt-3 text-sm text-red-500">{{ error || payError }}</p> <p v-if="error || payError" class="mt-3 text-sm text-red-500">{{ error || payError }}</p>
@@ -93,8 +135,8 @@ import { hjcOrderStatusKey } from '~/utils/orderStatus'
* 收银台是事后回到某张待支付订单继续付款。**本轮不重构 BuyDocument**, * 收银台是事后回到某张待支付订单继续付款。**本轮不重构 BuyDocument**,
* 两份实现共享的只有 `HjcPayQrcode` 与 `useHjcPayStatus`(重活已经都在那里了)。 * 两份实现共享的只有 `HjcPayQrcode` 与 `useHjcPayStatus`(重活已经都在那里了)。
* *
* 本页**只服务微信外**(ADR 0009):恒走 Native 扫码。不要在这里加"微信 UA 就跳 hjc-h5", * 本页只服务微信外(ADR 0008):不因 UA 在微信内就跳 hjc-h5。
* 那条链在 .scratch/hjc-web-pay-qrcode/issues/06 里被"hjc-h5 上线"阻塞着。 * 支付方式可选**微信扫码**或**支付宝**;支付宝走电脑网站支付(返回自提交表单)。
*/ */
const route = useRoute() const route = useRoute()
const { isLoggedIn, handleAuthCode } = useHjcAuth() const { isLoggedIn, handleAuthCode } = useHjcAuth()
@@ -112,16 +154,29 @@ const order = shallowRef<any | null>(null)
/** 终态(已完成/已取消/已退款):终态下**绝不发起支付** */ /** 终态(已完成/已取消/已退款):终态下**绝不发起支付** */
const finalState = shallowRef<{ title: string; cls: string } | null>(null) const finalState = shallowRef<{ title: string; cls: string } | null>(null)
/** 当前支付方式。默认微信,与接入支付宝之前的行为一致 */
const payType = ref<'wechat' | 'alipay'>('wechat')
const payOptions = [
{ value: 'wechat' as const, label: '微信支付' },
{ value: 'alipay' as const, label: '支付宝' }
]
const codeUrl = ref('') const codeUrl = ref('')
/** 支付宝电脑网站支付的自提交表单 */
const alipayForm = ref('')
const amount = ref(0) const amount = ref(0)
const starting = ref(false) const starting = ref(false)
const refreshing = ref(false) const refreshing = ref(false)
const confirming = ref(false) const confirming = ref(false)
const error = ref('') const error = ref('')
/** 提示文案里的渠道名(只影响措辞,不影响判定) */
const channelLabel = computed(() => (payType.value === 'alipay' ? '支付宝' : '微信'))
const qrAlt = computed(() => (payType.value === 'alipay' ? '支付宝支付二维码' : '微信支付二维码'))
/** /**
* 支付确认一律以**微信侧**为准(轮询 + 手动按钮都走它)。 * 支付确认一律以**渠道侧**为准(轮询 + 手动按钮都走它)。
* 绝不调用 `mark-paid`:那个接口不查单,点一下就能把订单置为已支付(见 ADR 0009)。 * 绝不调用 `mark-paid`:那个接口不查单,点一下就能把订单置为已支付(见 ADR 0008)。
*/ */
const { const {
paid, paid,
@@ -130,9 +185,9 @@ const {
expired: payExpired, expired: payExpired,
start: startPayPolling, start: startPayPolling,
query: queryPayStatus query: queryPayStatus
} = useHjcPayStatus(() => route.fullPath) } = useHjcPayStatus(() => route.fullPath, () => payType.value)
// 微信侧确认收款后跳订单列表(与 h5 收银台的 redirectTo 列表对齐) // 渠道侧确认收款后跳订单列表(与 h5 收银台的 redirectTo 列表对齐)
watch(paid, async (v) => { watch(paid, async (v) => {
if (v) await navigateTo('/tender/orders') if (v) await navigateTo('/tender/orders')
}) })
@@ -156,12 +211,12 @@ function terminalStateOf(o: any) {
} }
/** /**
* 微信错误码 → 可行动的中文提示。 * 渠道错误码 → 可行动的中文提示。
* *
* `OUT_TRADE_NO_USED` = 同一订单号换了支付类型重复下单。微信内(h5 → 小程序,JSAPI)与 * `OUT_TRADE_NO_USED` = 同一订单号换了支付类型重复下单。微信内(h5 → 小程序,JSAPI)与
* PC 端(Native)各发起一次时必现,而"从订单列表回到旧单支付"这条路径正是本轮新开的。 * PC 端(Native)各发起一次时必现,而"从订单列表回到旧单支付"这条路径正是本轮新开的。
* 错误码由后端从支付异常的 cause 链上取(`ServiceException.getErrorCode()`), * 错误码由后端从支付异常的 cause 链上取,前端**不要**去匹配 message 字符串——
* 前端**不要**去匹配 message 字符串——那种写法会在错误包装方式一改时静默失效。 * 那种写法会在错误包装方式一改时静默失效。
*/ */
function payErrorMessage(res: any) { function payErrorMessage(res: any) {
if (res?.data?.wechatCode === 'OUT_TRADE_NO_USED') { if (res?.data?.wechatCode === 'OUT_TRADE_NO_USED') {
@@ -174,7 +229,10 @@ async function startPay() {
error.value = '' error.value = ''
starting.value = true starting.value = true
try { try {
const res: any = await $fetch('/api/tender/pay', { method: 'POST', body: { orderNo: orderNo.value } }) const res: any = await $fetch('/api/tender/pay', {
method: 'POST',
body: { orderNo: orderNo.value, payType: payType.value }
})
const auth = await handleAuthCode(res, route.fullPath) const auth = await handleAuthCode(res, route.fullPath)
if (auth.handled) { if (auth.handled) {
error.value = auth.message || '' error.value = auth.message || ''
@@ -182,12 +240,18 @@ async function startPay() {
} }
if (res?.code === 0) { if (res?.code === 0) {
codeUrl.value = res.data?.codeUrl || '' codeUrl.value = res.data?.codeUrl || ''
alipayForm.value = res.data?.alipayForm || ''
if (res.data?.amount) amount.value = Number(res.data.amount) if (res.data?.amount) amount.value = Number(res.data.amount)
if (!codeUrl.value) error.value = '未获取到支付二维码,请稍后重试' if (!codeUrl.value && !alipayForm.value) {
error.value = '未获取到支付信息,请稍后重试'
} else if (alipayForm.value) {
// 网页支付:立刻把表单送去新页面,用户不必再点一次
openAlipayForm()
}
} else { } else {
error.value = payErrorMessage(res) error.value = payErrorMessage(res)
} }
// 无论这次有没有拿到支付码都开始查单:订单可能**已经**在微信侧付过了 // 无论这次有没有拿到支付码都开始查单:订单可能**已经**在渠道侧付过了
//(例如在微信内由小程序付掉、或上一次支付成功但页面被关掉),查单会把它认出来并跳列表。 //(例如在微信内由小程序付掉、或上一次支付成功但页面被关掉),查单会把它认出来并跳列表。
if (orderNo.value) startPayPolling(orderNo.value) if (orderNo.value) startPayPolling(orderNo.value)
} catch (e: any) { } catch (e: any) {
@@ -197,12 +261,45 @@ async function startPay() {
} }
} }
/** 重新获取二维码:微信允许对同一订单号重复发起 Native 下单,顺带重置 2 小时计时 */ /**
* 把支付宝返回的自提交表单写进一个新页面。
*
* 用 `document.write` 而不是 `v-html`:表单里带一段自动提交的脚本,
* 而 `v-html` 插入的脚本标签**不会执行**(HTML 规范如此),表单永远不会自动提交。
* 写进新窗口则脚本正常执行,且原收银台页面保留在原标签页继续轮询查单。
*
* 注意:本注释不要写出脚本标签的字面量——SFC 的 script 块会被它提前闭合。
*/
function openAlipayForm() {
if (!alipayForm.value) return
const w = window.open('', '_blank')
if (!w) {
// 弹窗被拦截:留在本页提示用户点按钮重试(按钮复用它)
error.value = '浏览器拦截了新窗口,请允许弹出窗口后点「前往支付宝付款」'
return
}
w.document.open()
w.document.write(alipayForm.value)
w.document.close()
error.value = ''
}
/** 切换支付方式:清掉上一张码再重新发起,避免两种支付信息同时留在页面上 */
async function switchPayType(next: 'wechat' | 'alipay') {
if (next === payType.value) return
payType.value = next
codeUrl.value = ''
alipayForm.value = ''
error.value = ''
await startPay()
}
/** 重新获取二维码:允许对同一订单号重复发起下单,顺带重置计时 */
async function refreshQr() { async function refreshQr() {
await startPay() await startPay()
} }
/** 手动确认(扫码后的兜底):**也走查单**,不代替微信下结论 */ /** 手动确认(扫码后的兜底):**也走查单**,不代替渠道下结论 */
async function confirmPaid() { async function confirmPaid() {
error.value = '' error.value = ''
confirming.value = true confirming.value = true
@@ -216,7 +313,7 @@ async function confirmPaid() {
await navigateTo('/tender/orders') await navigateTo('/tender/orders')
return return
} }
error.value = res.message || '微信侧还未收到付款,请完成扫码支付后再试' error.value = res.message || `${channelLabel.value}侧还未收到付款,请完成支付后再试`
} finally { } finally {
confirming.value = false confirming.value = false
} }
@@ -250,6 +347,11 @@ onMounted(async () => {
finalState.value = terminal finalState.value = terminal
return return
} }
// 已在这张单上发起过支付宝支付,就默认用支付宝继续——避免用户上次选了支付宝、
// 这次进来又被默认拉回微信,撞一次渠道侧的「订单号已使用」
if (typeof o?.payMethod === 'string' && o.payMethod.startsWith('ALIPAY')) {
payType.value = 'alipay'
}
// 只有"确实还没付、也没取消"才发起支付 // 只有"确实还没付、也没取消"才发起支付
loading.value = false loading.value = false
await startPay() await startPay()
+40 -39
View File
@@ -1,39 +1,40 @@
import { $fetch } from 'ofetch' import { $fetch } from 'ofetch'
import { createError, defineEventHandler, getHeader, getCookie, getQuery } from 'h3' import { createError, defineEventHandler, getHeader, getCookie, getQuery } from 'h3'
import { useRuntimeConfig } from '#imports' import { useRuntimeConfig } from '#imports'
import { getTenantFromContext } from '../../utils/tenant' import { getTenantFromContext } from '../../utils/tenant'
/** /**
* 查询微信侧支付状态(已支付则后端顺带修复本地订单状态) * 查询支付渠道侧的状态(已支付则后端顺带修复本地订单状态)
* GET /api/tender/pay-status?orderNo=xxx * GET /api/tender/pay-status?orderNo=xxx
* 代理到 mp-api /api/hjc/order/pay-status/{orderNo};需登录态。 * 代理到 mp-api /api/hjc/order/pay-status/{orderNo};需登录态。
* *
* 这是 hjc-web **唯一**的支付确认通道:它向微信查单,以微信侧的结论为准。 * 这是 hjc-web **唯一**的支付确认通道:它向渠道查单,以渠道侧的结论为准。
* 不要退回 mark-paid——那个接口不查单,点一下就能把订单置为已支付(见 ADR 0009)。 * 后端按订单的 pay_method 自动打到微信或支付宝,**前端不需要传渠道**。
* * 不要退回 mark-paid——那个接口不查单,点一下就能把订单置为已支付(见 ADR 0008)。
* 约定:原样透传 ApiResult{code,message,data},由前端按 code 判定登录态与业务失败。 *
*/ * 约定:原样透传 ApiResult{code,message,data},由前端按 code 判定登录态与业务失败。
export default defineEventHandler(async (event) => { */
const config = useRuntimeConfig() export default defineEventHandler(async (event) => {
const ctx = getTenantFromContext(event, config) const config = useRuntimeConfig()
const { orderNo } = getQuery(event) const ctx = getTenantFromContext(event, config)
const cookieToken = getCookie(event, 'hjc_token') const { orderNo } = getQuery(event)
const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null) const cookieToken = getCookie(event, 'hjc_token')
const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null)
try {
return await $fetch(`/hjc/order/pay-status/${encodeURIComponent(String(orderNo ?? ''))}`, { try {
baseURL: config.public.modulesApiBase, return await $fetch(`/hjc/order/pay-status/${encodeURIComponent(String(orderNo ?? ''))}`, {
method: 'GET', baseURL: config.public.modulesApiBase,
headers: { method: 'GET',
TenantId: ctx.tenantId, headers: {
...(auth ? { Authorization: auth } : {}) TenantId: ctx.tenantId,
}, ...(auth ? { Authorization: auth } : {})
query: { TenantId: ctx.tenantId } },
}) query: { TenantId: ctx.tenantId }
} catch (error: any) { })
throw createError({ } catch (error: any) {
statusCode: error?.statusCode || error?.response?.status || 502, throw createError({
statusMessage: error?.statusMessage || 'Failed to query pay status' statusCode: error?.statusCode || error?.response?.status || 502,
}) statusMessage: error?.statusMessage || 'Failed to query pay status'
} })
}) }
})
+40 -37
View File
@@ -1,37 +1,40 @@
import { $fetch } from 'ofetch' import { $fetch } from 'ofetch'
import { createError, defineEventHandler, readBody, getHeader, getCookie } from 'h3' import { createError, defineEventHandler, readBody, getHeader, getCookie } from 'h3'
import { useRuntimeConfig } from '#imports' import { useRuntimeConfig } from '#imports'
import { getTenantFromContext } from '../../utils/tenant' import { getTenantFromContext } from '../../utils/tenant'
/** /**
* 发起支付(微信Native扫码,返回 codeUrl) * 发起支付(微信 Native 扫码 / 支付宝电脑网站支付)
* POST /api/tender/pay { orderNo } * POST /api/tender/pay { orderNo, payType? }
* 代理到 mp-api /api/hjc/order/pay;需登录态。 * 代理到 mp-api /api/hjc/order/pay;需登录态。
* *
* 约定:原样透传 ApiResult{code,message,data},由前端按 code 判定登录态与业务失败。 * `payType`:`wechat`(默认,与接入支付宝前行为一致)/ `alipay`。
*/ * 请求体原样透传,因此新增支付方式**不需要改这个代理**。
export default defineEventHandler(async (event) => { *
const config = useRuntimeConfig() * 约定:原样透传 ApiResult{code,message,data},由前端按 code 判定登录态与业务失败。
const ctx = getTenantFromContext(event, config) */
const body = await readBody(event) export default defineEventHandler(async (event) => {
const cookieToken = getCookie(event, 'hjc_token') const config = useRuntimeConfig()
const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null) const ctx = getTenantFromContext(event, config)
const body = await readBody(event)
try { const cookieToken = getCookie(event, 'hjc_token')
return await $fetch('/hjc/order/pay', { const auth = getHeader(event, 'authorization') || (cookieToken ? `Bearer ${cookieToken}` : null)
baseURL: config.public.modulesApiBase,
method: 'POST', try {
headers: { return await $fetch('/hjc/order/pay', {
TenantId: ctx.tenantId, baseURL: config.public.modulesApiBase,
...(auth ? { Authorization: auth } : {}) method: 'POST',
}, headers: {
query: { TenantId: ctx.tenantId }, TenantId: ctx.tenantId,
body ...(auth ? { Authorization: auth } : {})
}) },
} catch (error: any) { query: { TenantId: ctx.tenantId },
throw createError({ body
statusCode: error?.statusCode || error?.response?.status || 502, })
statusMessage: error?.statusMessage || 'Failed to pay' } catch (error: any) {
}) throw createError({
} statusCode: error?.statusCode || error?.response?.status || 502,
}) statusMessage: error?.statusMessage || 'Failed to pay'
})
}
})