|
|
@@ -0,0 +1,102 @@
|
|
|
+const axios = require('axios')
|
|
|
+const config = require('../config.json')
|
|
|
+
|
|
|
+function getPaymentConfig() {
|
|
|
+ return config.pay || {}
|
|
|
+}
|
|
|
+
|
|
|
+function normalizePayBaseUrl(url) {
|
|
|
+ return String(url || '').trim().replace(/\/+$/, '')
|
|
|
+}
|
|
|
+
|
|
|
+function buildPaymentApiUrl(act, params = {}) {
|
|
|
+ const paymentConfig = getPaymentConfig()
|
|
|
+ const baseUrl = normalizePayBaseUrl(paymentConfig.url)
|
|
|
+ if (!baseUrl) {
|
|
|
+ throw new Error('支付配置错误')
|
|
|
+ }
|
|
|
+
|
|
|
+ const url = new URL(`${baseUrl}/api.php`)
|
|
|
+ url.searchParams.set('act', act)
|
|
|
+ for (const [key, value] of Object.entries(params)) {
|
|
|
+ if (value === undefined || value === null || value === '') continue
|
|
|
+ url.searchParams.set(key, String(value))
|
|
|
+ }
|
|
|
+ return url.toString()
|
|
|
+}
|
|
|
+
|
|
|
+function parsePaymentResponseBody(data) {
|
|
|
+ if (data && typeof data === 'object') return data
|
|
|
+ if (typeof data !== 'string') return null
|
|
|
+ const text = data.trim()
|
|
|
+ if (!text) return null
|
|
|
+ try {
|
|
|
+ return JSON.parse(text)
|
|
|
+ } catch (_) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function getAxiosPaymentOptions(extra = {}) {
|
|
|
+ return {
|
|
|
+ timeout: 15000,
|
|
|
+ maxRedirects: 5,
|
|
|
+ proxy: false,
|
|
|
+ validateStatus: (status) => status >= 200 && status < 500,
|
|
|
+ ...extra
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function formatPaymentHttpError(error, fallbackMessage) {
|
|
|
+ if (error?.code === 'ECONNABORTED') {
|
|
|
+ return '支付平台响应超时,请稍后重试'
|
|
|
+ }
|
|
|
+ if (error?.code === 'ERR_FR_TOO_MANY_REDIRECTS') {
|
|
|
+ return '支付平台重定向异常,请检查 pay.url 配置或关闭系统代理后重试'
|
|
|
+ }
|
|
|
+ const status = Number(error?.response?.status || 0)
|
|
|
+ const parsed = parsePaymentResponseBody(error?.response?.data)
|
|
|
+ if (parsed?.msg) return String(parsed.msg)
|
|
|
+ if (status === 503) return '支付平台暂时不可用,请稍后重试'
|
|
|
+ if (status >= 500) return `支付平台异常(${status}),请稍后重试`
|
|
|
+ return fallbackMessage || error?.message || '支付平台请求失败'
|
|
|
+}
|
|
|
+
|
|
|
+async function queryPaymentOrder(orderId, logger) {
|
|
|
+ const paymentConfig = getPaymentConfig()
|
|
|
+ if (!paymentConfig.pid || !paymentConfig.url || !paymentConfig.key) {
|
|
|
+ throw new Error('支付配置错误')
|
|
|
+ }
|
|
|
+
|
|
|
+ const queryUrl = buildPaymentApiUrl('order', {
|
|
|
+ pid: paymentConfig.pid,
|
|
|
+ key: paymentConfig.key,
|
|
|
+ out_trade_no: orderId
|
|
|
+ })
|
|
|
+
|
|
|
+ let response
|
|
|
+ try {
|
|
|
+ response = await axios.get(queryUrl, getAxiosPaymentOptions())
|
|
|
+ } catch (error) {
|
|
|
+ const message = formatPaymentHttpError(error, '查询支付状态失败')
|
|
|
+ logger?.warn?.(`查询支付状态失败,订单号:${orderId},URL:${queryUrl},原因:${message}`)
|
|
|
+ throw new Error(message)
|
|
|
+ }
|
|
|
+
|
|
|
+ const result = parsePaymentResponseBody(response.data)
|
|
|
+ if (!result) {
|
|
|
+ logger?.warn?.(`支付平台返回非 JSON,订单号:${orderId},HTTP ${response.status}`)
|
|
|
+ throw new Error('支付平台响应异常')
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+module.exports = {
|
|
|
+ normalizePayBaseUrl,
|
|
|
+ buildPaymentApiUrl,
|
|
|
+ getAxiosPaymentOptions,
|
|
|
+ formatPaymentHttpError,
|
|
|
+ parsePaymentResponseBody,
|
|
|
+ queryPaymentOrder
|
|
|
+}
|