| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698 |
- /**
- * 支付工具函数
- * 封装微信支付相关逻辑
- */
- import Taro from '@tarojs/taro'
- /**
- * 微信支付参数类型
- */
- export interface WechatPaymentParams {
- timeStamp: string
- nonceStr: string
- package: string
- signType: string
- paySign: string
- }
- /**
- * 支付结果类型
- */
- export interface PaymentResult {
- success: boolean
- type?: 'success' | 'cancel' | 'fail' | 'error'
- message?: string
- result?: any
- }
- /**
- * 调用微信支付
- * @param paymentData 支付参数
- * @returns 支付结果
- */
- export const requestWechatPayment = async (paymentData: WechatPaymentParams): Promise<PaymentResult> => {
- try {
- const result = await Taro.requestPayment({
- timeStamp: paymentData.timeStamp,
- nonceStr: paymentData.nonceStr,
- package: paymentData.package,
- signType: paymentData.signType as any, // Taro类型定义问题,使用any绕过
- paySign: paymentData.paySign
- })
- return {
- success: true,
- type: 'success',
- result
- }
- } catch (error: any) {
- console.error('微信支付调用失败:', error)
- // 根据错误码处理不同场景
- if (error.errMsg?.includes('cancel')) {
- return {
- success: false,
- type: 'cancel',
- message: '用户取消支付'
- }
- } else if (error.errMsg?.includes('fail')) {
- return {
- success: false,
- type: 'fail',
- message: '支付失败'
- }
- } else {
- return {
- success: false,
- type: 'error',
- message: error.errMsg || '支付异常'
- }
- }
- }
- }
- /**
- * 验证支付参数
- * @param paymentData 支付参数
- * @returns 验证结果
- */
- export const validatePaymentParams = (paymentData: WechatPaymentParams): { valid: boolean; errors: string[] } => {
- const errors: string[] = []
- if (!paymentData.timeStamp) {
- errors.push('时间戳不能为空')
- }
- if (!paymentData.nonceStr) {
- errors.push('随机字符串不能为空')
- }
- if (!paymentData.package) {
- errors.push('预支付ID不能为空')
- }
- if (!paymentData.signType) {
- errors.push('签名类型不能为空')
- }
- if (!paymentData.paySign) {
- errors.push('签名不能为空')
- }
- return {
- valid: errors.length === 0,
- errors
- }
- }
- /**
- * 处理支付金额
- * @param amount 金额(元)
- * @returns 金额(分)
- */
- export const formatPaymentAmount = (amount: number): number => {
- // 微信支付金额单位为分,需要乘以100
- return Math.round(amount * 100)
- }
- /**
- * 检查支付环境
- * @returns 是否支持微信支付
- */
- export const checkPaymentEnvironment = async (): Promise<boolean> => {
- try {
- // 检查是否在微信小程序环境中
- if (typeof Taro.requestPayment === 'undefined') {
- console.error('当前环境不支持微信支付')
- return false
- }
- // 可以添加更多环境检查逻辑
- return true
- } catch (error) {
- console.error('检查支付环境失败:', error)
- return false
- }
- }
- /**
- * 支付安全验证
- * @param orderId 订单ID
- * @param amount 支付金额
- * @param paymentParams 支付参数
- * @returns 验证结果
- */
- export const validatePaymentSecurity = (
- orderId: number,
- amount: number,
- paymentParams: WechatPaymentParams
- ): { valid: boolean; reason?: string } => {
- // 验证订单ID
- if (!orderId || orderId <= 0) {
- return { valid: false, reason: '订单ID无效' }
- }
- // 验证金额
- if (!amount || amount <= 0) {
- return { valid: false, reason: '支付金额无效' }
- }
- // 验证支付参数
- const paramValidation = validatePaymentParams(paymentParams)
- if (!paramValidation.valid) {
- return {
- valid: false,
- reason: `支付参数错误: ${paramValidation.errors.join(', ')}`
- }
- }
- // 时间戳验证(防止重放攻击)
- const timestamp = parseInt(paymentParams.timeStamp)
- const currentTime = Math.floor(Date.now() / 1000)
- const timeDiff = Math.abs(currentTime - timestamp)
- // 时间戳应该在5分钟内有效
- if (timeDiff > 300) {
- return { valid: false, reason: '支付参数已过期,请重新发起支付' }
- }
- // 随机字符串长度验证 - 放宽限制以适应微信支付实际返回
- if (!paymentParams.nonceStr || paymentParams.nonceStr.length < 8) {
- return { valid: false, reason: '随机字符串长度无效' }
- }
- // 签名类型验证 - 放宽限制以适应微信支付实际返回
- if (!paymentParams.signType) {
- return { valid: false, reason: '签名类型不能为空' }
- }
- // 验证签名类型是否支持
- const supportedSignTypes = ['RSA', 'HMAC-SHA256']
- if (!supportedSignTypes.includes(paymentParams.signType)) {
- return { valid: false, reason: '签名类型不支持' }
- }
- // 预支付ID格式验证 - 放宽限制以适应微信支付实际返回
- if (!paymentParams.package) {
- return { valid: false, reason: '预支付ID不能为空' }
- }
- // 签名长度验证 - 放宽限制以适应微信支付实际返回
- if (!paymentParams.paySign || paymentParams.paySign.length < 16) {
- return { valid: false, reason: '签名长度过短' }
- }
- return { valid: true }
- }
- /**
- * 生成支付参数哈希(用于防篡改验证)
- * @param paymentParams 支付参数
- * @returns 参数哈希
- */
- export const generatePaymentParamsHash = (paymentParams: WechatPaymentParams): string => {
- const paramsString = [
- paymentParams.timeStamp,
- paymentParams.nonceStr,
- paymentParams.package,
- paymentParams.signType
- ].join('&')
- // 在实际项目中,这里应该使用更安全的哈希算法
- // 这里使用简单的哈希作为示例
- let hash = 0
- for (let i = 0; i < paramsString.length; i++) {
- const char = paramsString.charCodeAt(i)
- hash = ((hash << 5) - hash) + char
- hash = hash & hash // 转换为32位整数
- }
- return Math.abs(hash).toString(16)
- }
- /**
- * 验证支付参数完整性
- * @param originalParams 原始支付参数
- * @param receivedParams 接收到的支付参数
- * @returns 验证结果
- */
- export const verifyPaymentParamsIntegrity = (
- originalParams: WechatPaymentParams,
- receivedParams: WechatPaymentParams
- ): { valid: boolean; reason?: string } => {
- const originalHash = generatePaymentParamsHash(originalParams)
- const receivedHash = generatePaymentParamsHash(receivedParams)
- if (originalHash !== receivedHash) {
- return { valid: false, reason: '支付参数被篡改' }
- }
- return { valid: true }
- }
- /**
- * 支付金额一致性验证
- * @param expectedAmount 预期金额(元)
- * @param paymentAmount 支付金额(分)
- * @returns 验证结果
- */
- export const validateAmountConsistency = (
- expectedAmount: number,
- paymentAmount: number
- ): { valid: boolean; reason?: string } => {
- const expectedInFen = Math.round(expectedAmount * 100)
- if (expectedInFen !== paymentAmount) {
- return {
- valid: false,
- reason: `金额不一致: 预期 ${expectedInFen} 分,实际 ${paymentAmount} 分`
- }
- }
- return { valid: true }
- }
- /**
- * 支付频率限制检查
- */
- export class PaymentRateLimiter {
- private static instance: PaymentRateLimiter
- private attempts: Map<number, number[]> = new Map()
- private readonly MAX_ATTEMPTS = 5
- private readonly TIME_WINDOW = 60000 // 1分钟
- private constructor() {}
- static getInstance(): PaymentRateLimiter {
- if (!PaymentRateLimiter.instance) {
- PaymentRateLimiter.instance = new PaymentRateLimiter()
- }
- return PaymentRateLimiter.instance
- }
- /**
- * 检查是否超过支付频率限制
- */
- isRateLimited(orderId: number): { limited: boolean; remainingTime?: number } {
- const now = Date.now()
- const attempts = this.attempts.get(orderId) || []
- // 清理过期的尝试记录
- const recentAttempts = attempts.filter(time => now - time < this.TIME_WINDOW)
- this.attempts.set(orderId, recentAttempts)
- if (recentAttempts.length >= this.MAX_ATTEMPTS) {
- const oldestAttempt = Math.min(...recentAttempts)
- const remainingTime = this.TIME_WINDOW - (now - oldestAttempt)
- return { limited: true, remainingTime }
- }
- return { limited: false }
- }
- /**
- * 记录支付尝试
- */
- recordAttempt(orderId: number): void {
- const attempts = this.attempts.get(orderId) || []
- attempts.push(Date.now())
- this.attempts.set(orderId, attempts)
- }
- /**
- * 清除支付尝试记录
- */
- clearAttempts(orderId: number): void {
- this.attempts.delete(orderId)
- }
- }
- /**
- * 支付重试逻辑
- * @param paymentFn 支付函数
- * @param maxRetries 最大重试次数
- * @param delay 重试延迟(毫秒)
- * @returns 支付结果
- */
- export const retryPayment = async (
- paymentFn: () => Promise<PaymentResult>,
- maxRetries: number = 3,
- delay: number = 1000
- ): Promise<PaymentResult> => {
- let lastError: any = null
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
- try {
- const result = await paymentFn()
- if (result.success) {
- return result
- }
- // 如果是用户取消,不重试
- if (result.type === 'cancel') {
- return result
- }
- lastError = result
- if (attempt < maxRetries) {
- console.log(`支付失败,第${attempt}次重试...`)
- await new Promise(resolve => setTimeout(resolve, delay))
- }
- } catch (error) {
- lastError = error
- if (attempt < maxRetries) {
- console.log(`支付异常,第${attempt}次重试...`)
- await new Promise(resolve => setTimeout(resolve, delay))
- }
- }
- }
- return {
- success: false,
- type: 'error',
- message: `支付失败,已重试${maxRetries}次: ${lastError?.message || '未知错误'}`
- }
- }
- /**
- * 支付状态枚举
- */
- export enum PaymentStatus {
- PENDING = '待支付',
- PROCESSING = '支付中',
- SUCCESS = '支付成功',
- FAILED = '支付失败',
- REFUNDED = '已退款',
- CLOSED = '已关闭'
- }
- /**
- * 支付状态管理类
- */
- export class PaymentStateManager {
- private static instance: PaymentStateManager
- private state: Map<number, PaymentStatus> = new Map()
- private stateHistory: Map<number, { status: PaymentStatus; timestamp: number }[]> = new Map()
- private constructor() {}
- static getInstance(): PaymentStateManager {
- if (!PaymentStateManager.instance) {
- PaymentStateManager.instance = new PaymentStateManager()
- }
- return PaymentStateManager.instance
- }
- /**
- * 设置支付状态
- */
- setPaymentState(orderId: number, status: PaymentStatus): void {
- const previousStatus = this.state.get(orderId)
- this.state.set(orderId, status)
- // 记录状态历史
- const history = this.stateHistory.get(orderId) || []
- history.push({
- status,
- timestamp: Date.now()
- })
- this.stateHistory.set(orderId, history)
- console.log(`订单 ${orderId} 支付状态流转: ${previousStatus || '初始'} -> ${status}`)
- }
- /**
- * 获取支付状态
- */
- getPaymentState(orderId: number): PaymentStatus | undefined {
- return this.state.get(orderId)
- }
- /**
- * 获取支付状态历史
- */
- getPaymentStateHistory(orderId: number): { status: PaymentStatus; timestamp: number }[] {
- return this.stateHistory.get(orderId) || []
- }
- /**
- * 检查是否重复支付
- */
- isDuplicatePayment(orderId: number): boolean {
- const currentStatus = this.getPaymentState(orderId)
- return currentStatus === PaymentStatus.PROCESSING || currentStatus === PaymentStatus.SUCCESS
- }
- /**
- * 清除支付状态
- */
- clearPaymentState(orderId: number): void {
- this.state.delete(orderId)
- this.stateHistory.delete(orderId)
- }
- /**
- * 获取所有支付状态
- */
- getAllPaymentStates(): Map<number, PaymentStatus> {
- return new Map(this.state)
- }
- /**
- * 验证状态流转是否合法
- */
- validateStateTransition(orderId: number, newStatus: PaymentStatus): { valid: boolean; reason?: string } {
- const currentStatus = this.getPaymentState(orderId)
- // 状态流转规则
- const allowedTransitions: Record<PaymentStatus, PaymentStatus[]> = {
- [PaymentStatus.PENDING]: [PaymentStatus.PROCESSING, PaymentStatus.CLOSED],
- [PaymentStatus.PROCESSING]: [PaymentStatus.SUCCESS, PaymentStatus.FAILED, PaymentStatus.CLOSED],
- [PaymentStatus.SUCCESS]: [PaymentStatus.REFUNDED],
- [PaymentStatus.FAILED]: [PaymentStatus.PROCESSING, PaymentStatus.CLOSED],
- [PaymentStatus.REFUNDED]: [],
- [PaymentStatus.CLOSED]: []
- }
- // 初始状态允许任何流转
- if (!currentStatus) {
- return { valid: true }
- }
- // 检查是否允许流转
- const allowed = allowedTransitions[currentStatus] || []
- if (!allowed.includes(newStatus)) {
- return {
- valid: false,
- reason: `状态流转不合法: ${currentStatus} -> ${newStatus}`
- }
- }
- return { valid: true }
- }
- /**
- * 安全设置支付状态(带验证)
- */
- safeSetPaymentState(orderId: number, status: PaymentStatus): { success: boolean; reason?: string } {
- const validation = this.validateStateTransition(orderId, status)
- if (!validation.valid) {
- return { success: false, reason: validation.reason }
- }
- this.setPaymentState(orderId, status)
- return { success: true }
- }
- }
- /**
- * 支付超时处理
- * @param orderId 订单ID
- * @param timeout 超时时间(毫秒)
- * @returns 超时Promise
- */
- export const createPaymentTimeout = (timeout: number = 30000): Promise<PaymentResult> => {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve({
- success: false,
- type: 'error',
- message: '支付超时,请检查网络或重试'
- })
- }, timeout)
- })
- }
- /**
- * 支付状态同步
- * @param orderId 订单ID
- * @param expectedStatus 期望状态
- * @param maxAttempts 最大尝试次数
- * @param interval 检查间隔(毫秒)
- * @returns 同步结果
- */
- export const syncPaymentStatus = async (
- orderId: number,
- expectedStatus: PaymentStatus,
- maxAttempts: number = 10,
- interval: number = 2000
- ): Promise<{ synced: boolean; currentStatus?: PaymentStatus }> => {
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
- try {
- // 这里可以调用后端API查询实际支付状态
- // 暂时使用状态管理器模拟
- const stateManager = PaymentStateManager.getInstance()
- const currentStatus = stateManager.getPaymentState(orderId)
- if (currentStatus === expectedStatus) {
- return { synced: true, currentStatus }
- }
- console.log(`支付状态同步中... 第${attempt}次检查,当前状态: ${currentStatus}`)
- if (attempt < maxAttempts) {
- await new Promise(resolve => setTimeout(resolve, interval))
- }
- } catch (error) {
- console.error(`支付状态同步失败,第${attempt}次尝试:`, error)
- }
- }
- return { synced: false }
- }
- /**
- * 支付状态流转管理器
- */
- export class PaymentStateFlowManager {
- private static instance: PaymentStateFlowManager
- private stateManager: PaymentStateManager
- private rateLimiter: PaymentRateLimiter
- private constructor() {
- this.stateManager = PaymentStateManager.getInstance()
- this.rateLimiter = PaymentRateLimiter.getInstance()
- }
- static getInstance(): PaymentStateFlowManager {
- if (!PaymentStateFlowManager.instance) {
- PaymentStateFlowManager.instance = new PaymentStateFlowManager()
- }
- return PaymentStateFlowManager.instance
- }
- /**
- * 开始支付流程
- */
- async startPaymentFlow(orderId: number): Promise<{ success: boolean; reason?: string }> {
- // 检查频率限制
- const rateLimit = this.rateLimiter.isRateLimited(orderId)
- if (rateLimit.limited) {
- return {
- success: false,
- reason: `支付频率过高,请${Math.ceil(rateLimit.remainingTime! / 1000)}秒后重试`
- }
- }
- // 检查是否重复支付
- if (this.stateManager.isDuplicatePayment(orderId)) {
- return {
- success: false,
- reason: '订单正在支付中或已支付成功,请勿重复操作'
- }
- }
- // 设置支付中状态
- const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.PROCESSING)
- if (!setResult.success) {
- return setResult
- }
- // 记录支付尝试
- this.rateLimiter.recordAttempt(orderId)
- return { success: true }
- }
- /**
- * 处理支付成功
- */
- handlePaymentSuccess(orderId: number): { success: boolean; reason?: string } {
- const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.SUCCESS)
- if (setResult.success) {
- // 清除频率限制记录
- this.rateLimiter.clearAttempts(orderId)
- }
- return setResult
- }
- /**
- * 处理支付失败
- */
- handlePaymentFailure(orderId: number): { success: boolean; reason?: string } {
- const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.FAILED)
- return setResult
- }
- /**
- * 处理支付取消
- */
- handlePaymentCancel(orderId: number): { success: boolean; reason?: string } {
- const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.CLOSED)
- if (setResult.success) {
- // 清除频率限制记录
- this.rateLimiter.clearAttempts(orderId)
- }
- return setResult
- }
- /**
- * 处理退款
- */
- handleRefund(orderId: number): { success: boolean; reason?: string } {
- return this.stateManager.safeSetPaymentState(orderId, PaymentStatus.REFUNDED)
- }
- /**
- * 获取支付状态历史
- */
- getPaymentHistory(orderId: number): { status: PaymentStatus; timestamp: number }[] {
- return this.stateManager.getPaymentStateHistory(orderId)
- }
- /**
- * 检查是否可以重试支付
- */
- canRetryPayment(orderId: number): { canRetry: boolean; reason?: string } {
- const currentStatus = this.stateManager.getPaymentState(orderId)
- if (currentStatus === PaymentStatus.FAILED) {
- return { canRetry: true }
- }
- if (currentStatus === PaymentStatus.PROCESSING) {
- return { canRetry: false, reason: '订单正在支付中,请勿重复操作' }
- }
- if (currentStatus === PaymentStatus.SUCCESS) {
- return { canRetry: false, reason: '订单已支付成功,无需重复支付' }
- }
- if (currentStatus === PaymentStatus.CLOSED) {
- return { canRetry: false, reason: '订单已关闭,无法支付' }
- }
- return { canRetry: true }
- }
- /**
- * 重置支付状态
- */
- resetPaymentState(orderId: number): void {
- this.stateManager.clearPaymentState(orderId)
- this.rateLimiter.clearAttempts(orderId)
- }
- }
|