345 lines
8.5 KiB
TypeScript
345 lines
8.5 KiB
TypeScript
/**
|
||
* 支付模块 API 封装
|
||
* 支持:微信 JSAPI / 支付宝 / Native / 余额
|
||
*/
|
||
import request from '@/utils/request'
|
||
import type { ApiResult } from '@/api'
|
||
import { SERVER_API_URL } from '@/config/setting'
|
||
|
||
/** 支付方式 */
|
||
export type PayType = 'wechat' | 'alipay' | 'native' | 'balance' | number
|
||
|
||
/** 支付渠道 */
|
||
export type PayChannel = 'wechat_jsapi' | 'alipay_wap' | 'wechat_native' | 'balance'
|
||
|
||
/** 支付参数 */
|
||
export interface PayParams {
|
||
orderNo: string
|
||
orderId?: number
|
||
payType: PayChannel
|
||
/** 微信 JSAPI 必填:用户 openid */
|
||
openId?: string
|
||
/** 微信 JSAPI 必填:支付主体类型 */
|
||
subject?: string
|
||
/** 支付主体描述 */
|
||
body?: string
|
||
/** 支付金额(分) */
|
||
totalAmount?: number
|
||
/** 前端回调 URL */
|
||
returnUrl?: string
|
||
}
|
||
|
||
/** 微信 JSAPI 支付参数 */
|
||
export interface WechatJsapiParams {
|
||
orderNo: string
|
||
openId: string
|
||
subject: string
|
||
body?: string
|
||
totalAmount?: number
|
||
returnUrl?: string
|
||
}
|
||
|
||
/** 支付宝 WAP/Web 支付参数 */
|
||
export interface AlipayParams {
|
||
orderNo: string
|
||
subject: string
|
||
body?: string
|
||
totalAmount?: number
|
||
returnUrl?: string
|
||
}
|
||
|
||
/** Native 支付参数 */
|
||
export interface NativeParams {
|
||
orderNo: string
|
||
subject: string
|
||
body?: string
|
||
totalAmount?: number
|
||
}
|
||
|
||
/** 支付结果 */
|
||
export interface PayResult {
|
||
codeUrl?: string // Native 支付二维码链接
|
||
qrcode?: string // 二维码内容
|
||
paymentUrl?: string // 跳转支付链接
|
||
payUrl?: string // 支付链接
|
||
prepayId?: string // 预支付订单号
|
||
mwebUrl?: string // 微信 H5 支付链接
|
||
tradeNo?: string // 交易流水号
|
||
orderNo?: string // 订单号
|
||
orderId?: number // 订单ID
|
||
}
|
||
|
||
/** 支付状态 */
|
||
export interface PayStatus {
|
||
paid: boolean
|
||
payStatus: number // 0 未支付,1 已支付
|
||
orderStatus: number // 订单状态
|
||
payTime?: string // 支付时间
|
||
transactionId?: string // 微信/支付宝交易号
|
||
}
|
||
|
||
/**
|
||
* 统一支付接口
|
||
* @deprecated 使用具体渠道接口
|
||
*/
|
||
export async function createPayment(data: Record<string, unknown>): Promise<PayResult> {
|
||
const res = await request.post<ApiResult<PayResult>>(
|
||
SERVER_API_URL + '/system/payment/create',
|
||
data
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 微信 JSAPI 支付
|
||
* 适用于微信内置浏览器 / 公众号 / 小程序环境
|
||
*/
|
||
export async function createWechatJsapiPay(params: WechatJsapiParams): Promise<PayResult> {
|
||
const res = await request.post<ApiResult<PayResult>>(
|
||
SERVER_API_URL + '/system/wx-jsapi-pay/unified-order',
|
||
params
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 微信 H5 支付
|
||
* 适用于非微信浏览器
|
||
*/
|
||
export async function createWechatH5Pay(params: { orderNo: string; subject: string; body?: string; totalAmount?: number; returnUrl?: string }): Promise<PayResult> {
|
||
const res = await request.post<ApiResult<PayResult>>(
|
||
SERVER_API_URL + '/system/wx-h5-pay/unified-order',
|
||
params
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 微信 Native 支付(扫码支付)
|
||
*/
|
||
export async function createWechatNativePay(params: NativeParams): Promise<PayResult> {
|
||
const res = await request.post<ApiResult<PayResult>>(
|
||
SERVER_API_URL + '/system/wx-native-pay/unified-order',
|
||
params
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 支付宝 WAP/Web 支付
|
||
*/
|
||
export async function createAlipayPay(params: AlipayParams): Promise<PayResult> {
|
||
const res = await request.post<ApiResult<PayResult>>(
|
||
SERVER_API_URL + '/system/alipay/unified-order',
|
||
params
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 查询支付状态
|
||
*/
|
||
export async function queryPayStatus(orderNo: string): Promise<PayStatus> {
|
||
const res = await request.get<ApiResult<PayStatus>>(
|
||
SERVER_API_URL + '/system/payment/query-status',
|
||
{ params: { orderNo } }
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 查询充值状态(专门用于充值订单)
|
||
*/
|
||
export async function queryRechargeStatus(orderNo: string): Promise<{
|
||
paid: boolean
|
||
payStatus: number
|
||
balance?: number
|
||
}> {
|
||
const res = await request.get<ApiResult<{
|
||
paid: boolean
|
||
payStatus: number
|
||
balance?: number
|
||
}>>(
|
||
SERVER_API_URL + '/system/wx-native-pay/recharge-status',
|
||
{ params: { orderNo } }
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 手动确认充值(测试用,生产环境由支付回调触发)
|
||
*/
|
||
export async function confirmRecharge(orderNo: string): Promise<{
|
||
orderNo: string
|
||
paid: boolean
|
||
payPrice: number
|
||
giftMoney: number
|
||
actualMoney: number
|
||
balance: number
|
||
}> {
|
||
const res = await request.post<ApiResult<{
|
||
orderNo: string
|
||
paid: boolean
|
||
payPrice: number
|
||
giftMoney: number
|
||
actualMoney: number
|
||
balance: number
|
||
}>>(
|
||
SERVER_API_URL + '/system/wx-native-pay/confirm-recharge',
|
||
null,
|
||
{ params: { orderNo } }
|
||
)
|
||
if (res.data.code === 0) {
|
||
return res.data.data!
|
||
}
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
|
||
/**
|
||
* 取消订单
|
||
*/
|
||
export async function cancelOrder(orderId: number): Promise<void> {
|
||
const res = await request.post<ApiResult<void>>(
|
||
SERVER_API_URL + '/system/order/cancel',
|
||
{ orderId }
|
||
)
|
||
if (res.data.code !== 0) {
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 申请退款
|
||
*/
|
||
export async function applyRefund(orderId: number, reason?: string): Promise<void> {
|
||
const res = await request.post<ApiResult<void>>(
|
||
SERVER_API_URL + '/system/order/refund',
|
||
{ orderId, reason }
|
||
)
|
||
if (res.data.code !== 0) {
|
||
return Promise.reject(new Error(res.data.message))
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 判断当前环境
|
||
*/
|
||
export function detectPayEnvironment(): 'wechat' | 'alipay' | 'desktop' | 'mobile' {
|
||
const ua = navigator.userAgent.toLowerCase()
|
||
|
||
// 微信环境
|
||
if (/micromessenger/.test(ua)) {
|
||
return 'wechat'
|
||
}
|
||
|
||
// 支付宝环境
|
||
if (/alipayclient/.test(ua)) {
|
||
return 'alipay'
|
||
}
|
||
|
||
// 移动端
|
||
if (/mobile|android|iphone|ipad|tablet/i.test(ua)) {
|
||
return 'mobile'
|
||
}
|
||
|
||
// 桌面端
|
||
return 'desktop'
|
||
}
|
||
|
||
/**
|
||
* 检测是否在微信内置浏览器
|
||
*/
|
||
export function isWechatBrowser(): boolean {
|
||
return /micromessenger/.test(navigator.userAgent.toLowerCase())
|
||
}
|
||
|
||
/**
|
||
* 检测是否在支付宝内置浏览器
|
||
*/
|
||
export function isAlipayBrowser(): boolean {
|
||
return /alipayclient/.test(navigator.userAgent.toLowerCase())
|
||
}
|
||
|
||
/**
|
||
* 调起微信 JSAPI 支付
|
||
* 需要先引入微信 JSSDK 并完成签名
|
||
*/
|
||
export function callWechatJsapi(params: {
|
||
appId: string
|
||
timestamp: string
|
||
nonceStr: string
|
||
package: string
|
||
signType?: string
|
||
paySign: string
|
||
}): Promise<'ok'> {
|
||
return new Promise((resolve, reject) => {
|
||
if (typeof window.WeixinJSBridge === 'undefined') {
|
||
// 尝试通过 WeChat JSSDK 调用
|
||
if (typeof window.jWeixin !== 'undefined') {
|
||
window.jWeixin.chooseWXPay({
|
||
...params,
|
||
success: () => resolve('ok'),
|
||
fail: (err: unknown) => reject(err)
|
||
})
|
||
} else {
|
||
reject(new Error('微信 JSSDK 未加载'))
|
||
}
|
||
return
|
||
}
|
||
|
||
window.WeixinJSBridge.invoke(
|
||
'getBrandWCPayRequest',
|
||
{
|
||
appId: params.appId,
|
||
timeStamp: params.timestamp,
|
||
nonceStr: params.nonceStr,
|
||
package: params.package,
|
||
signType: params.signType || 'MD5',
|
||
paySign: params.paySign
|
||
},
|
||
(res: { err_msg: string }) => {
|
||
if (res.err_msg === 'get_brand_wcpay_request:ok') {
|
||
resolve('ok')
|
||
} else if (res.err_msg === 'get_brand_wcpay_request:cancel') {
|
||
reject(new Error('用户取消支付'))
|
||
} else {
|
||
reject(new Error(res.err_msg))
|
||
}
|
||
}
|
||
)
|
||
})
|
||
}
|
||
|
||
// 扩展 Window 类型
|
||
declare global {
|
||
interface Window {
|
||
WeixinJSBridge?: {
|
||
invoke: (api: string, params: Record<string, unknown>, callback: (res: { err_msg: string }) => void) => void
|
||
}
|
||
jWeixin?: {
|
||
chooseWXPay: (params: Record<string, unknown> & { success?: () => void; fail?: (err: unknown) => void }) => void
|
||
}
|
||
}
|
||
}
|