| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338 |
- /**
- * 支付工具函数单元测试
- */
- import {
- validatePaymentParams,
- validatePaymentSecurity,
- formatPaymentAmount,
- PaymentStateManager,
- PaymentStatus,
- retryPayment,
- PaymentRateLimiter,
- validateAmountConsistency,
- generatePaymentParamsHash,
- verifyPaymentParamsIntegrity
- } from '@/utils/payment'
- import { mockRequestPayment } from '~/__mocks__/taroMock'
- describe('Payment Utils', () => {
- beforeEach(() => {
- mockRequestPayment.mockClear()
- // 清除状态管理器实例
- const stateManager = PaymentStateManager.getInstance()
- const allStates = stateManager.getAllPaymentStates()
- allStates.forEach((_, orderId) => {
- stateManager.clearPaymentState(orderId)
- })
- })
- describe('validatePaymentParams', () => {
- it('should validate correct payment parameters', () => {
- const validParams = {
- timeStamp: '1234567890',
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'abcdefghijklmnopqrstuvwxyz1234567890'
- }
- const result = validatePaymentParams(validParams)
- expect(result.valid).toBe(true)
- expect(result.errors).toHaveLength(0)
- })
- it('should detect missing parameters', () => {
- const invalidParams = {
- timeStamp: '',
- nonceStr: '',
- package: '',
- signType: '',
- paySign: ''
- }
- const result = validatePaymentParams(invalidParams)
- expect(result.valid).toBe(false)
- expect(result.errors).toHaveLength(5)
- })
- })
- describe('validatePaymentSecurity', () => {
- it('should validate secure payment parameters', () => {
- const validParams = {
- timeStamp: Math.floor(Date.now() / 1000).toString(),
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'abcdefghijklmnopqrstuvwxyz1234567890'
- }
- const result = validatePaymentSecurity(123, 100, validParams)
- expect(result.valid).toBe(true)
- })
- it('should detect expired timestamp', () => {
- const expiredParams = {
- timeStamp: '1000000000', // 很旧的时间戳
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'abcdefghijklmnopqrstuvwxyz1234567890'
- }
- const result = validatePaymentSecurity(123, 100, expiredParams)
- expect(result.valid).toBe(false)
- expect(result.reason).toContain('支付参数已过期')
- })
- it('should detect invalid sign type', () => {
- const invalidParams = {
- timeStamp: Math.floor(Date.now() / 1000).toString(),
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'INVALID',
- paySign: 'abcdefghijklmnopqrstuvwxyz1234567890'
- }
- const result = validatePaymentSecurity(123, 100, invalidParams)
- expect(result.valid).toBe(false)
- expect(result.reason).toContain('签名类型不支持')
- })
- })
- describe('formatPaymentAmount', () => {
- it('should convert yuan to fen correctly', () => {
- expect(formatPaymentAmount(100)).toBe(10000)
- expect(formatPaymentAmount(50.5)).toBe(5050)
- expect(formatPaymentAmount(0.01)).toBe(1)
- })
- it('should round fractional amounts', () => {
- expect(formatPaymentAmount(100.499)).toBe(10050) // 100.499 * 100 = 10049.9,四舍五入为10050
- expect(formatPaymentAmount(100.501)).toBe(10050)
- })
- })
- describe('PaymentStateManager', () => {
- it('should manage payment states correctly', () => {
- const stateManager = PaymentStateManager.getInstance()
- stateManager.setPaymentState(123, PaymentStatus.PROCESSING)
- expect(stateManager.getPaymentState(123)).toBe(PaymentStatus.PROCESSING)
- stateManager.setPaymentState(123, PaymentStatus.SUCCESS)
- expect(stateManager.getPaymentState(123)).toBe(PaymentStatus.SUCCESS)
- })
- it('should detect duplicate payments', () => {
- const stateManager = PaymentStateManager.getInstance()
- stateManager.setPaymentState(123, PaymentStatus.PROCESSING)
- expect(stateManager.isDuplicatePayment(123)).toBe(true)
- stateManager.setPaymentState(123, PaymentStatus.SUCCESS)
- expect(stateManager.isDuplicatePayment(123)).toBe(true)
- stateManager.setPaymentState(123, PaymentStatus.FAILED)
- expect(stateManager.isDuplicatePayment(123)).toBe(false)
- })
- it('should clear payment states', () => {
- const stateManager = PaymentStateManager.getInstance()
- stateManager.setPaymentState(123, PaymentStatus.PROCESSING)
- stateManager.clearPaymentState(123)
- expect(stateManager.getPaymentState(123)).toBeUndefined()
- })
- })
- describe('retryPayment', () => {
- it('should succeed on first attempt', async () => {
- // 直接模拟 requestWechatPayment 函数返回成功结果
- const mockRequestWechatPayment = jest.fn()
- mockRequestWechatPayment.mockResolvedValueOnce({
- success: true,
- type: 'success',
- result: {}
- })
- const paymentFn = async () => {
- return await mockRequestWechatPayment()
- }
- const result = await retryPayment(paymentFn)
- expect(result.success).toBe(true)
- expect(result.type).toBe('success')
- expect(mockRequestWechatPayment).toHaveBeenCalledTimes(1)
- })
- it('should retry on failure', async () => {
- // 直接模拟 requestWechatPayment 函数,第一次失败,第二次成功
- const mockRequestWechatPayment = jest.fn()
- mockRequestWechatPayment
- .mockResolvedValueOnce({
- success: false,
- type: 'error',
- message: '网络错误'
- })
- .mockResolvedValueOnce({
- success: true,
- type: 'success',
- result: {}
- })
- const paymentFn = async () => {
- return await mockRequestWechatPayment()
- }
- const result = await retryPayment(paymentFn, 3, 10)
- expect(result.success).toBe(true)
- expect(result.type).toBe('success')
- expect(mockRequestWechatPayment).toHaveBeenCalledTimes(2)
- })
- it('should not retry on user cancellation', async () => {
- // 直接模拟 requestWechatPayment 函数返回用户取消结果
- const mockRequestWechatPayment = jest.fn()
- mockRequestWechatPayment.mockResolvedValueOnce({
- success: false,
- type: 'cancel',
- message: '用户取消支付'
- })
- const paymentFn = async () => {
- return await mockRequestWechatPayment()
- }
- const result = await retryPayment(paymentFn)
- expect(result.success).toBe(false)
- expect(result.type).toBe('cancel')
- expect(result.message).toBe('用户取消支付')
- expect(mockRequestWechatPayment).toHaveBeenCalledTimes(1)
- })
- })
- describe('PaymentRateLimiter', () => {
- it('should allow payments within rate limit', () => {
- const rateLimiter = PaymentRateLimiter.getInstance()
- for (let i = 0; i < 4; i++) {
- rateLimiter.recordAttempt(123)
- }
- const result = rateLimiter.isRateLimited(123)
- expect(result.limited).toBe(false)
- })
- it('should block payments exceeding rate limit', () => {
- const rateLimiter = PaymentRateLimiter.getInstance()
- for (let i = 0; i < 5; i++) {
- rateLimiter.recordAttempt(123)
- }
- const result = rateLimiter.isRateLimited(123)
- expect(result.limited).toBe(true)
- expect(result.remainingTime).toBeGreaterThan(0)
- })
- it('should clear attempts', () => {
- const rateLimiter = PaymentRateLimiter.getInstance()
- rateLimiter.recordAttempt(123)
- rateLimiter.clearAttempts(123)
- const result = rateLimiter.isRateLimited(123)
- expect(result.limited).toBe(false)
- })
- })
- describe('validateAmountConsistency', () => {
- it('should validate consistent amounts', () => {
- const result = validateAmountConsistency(100, 10000)
- expect(result.valid).toBe(true)
- })
- it('should detect inconsistent amounts', () => {
- const result = validateAmountConsistency(100, 9999)
- expect(result.valid).toBe(false)
- expect(result.reason).toContain('金额不一致')
- })
- })
- describe('generatePaymentParamsHash', () => {
- it('should generate consistent hash for same parameters', () => {
- const params = {
- timeStamp: '1234567890',
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'signature'
- }
- const hash1 = generatePaymentParamsHash(params)
- const hash2 = generatePaymentParamsHash(params)
- expect(hash1).toBe(hash2)
- })
- it('should generate different hash for different parameters', () => {
- const params1 = {
- timeStamp: '1234567890',
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'signature'
- }
- const params2 = {
- timeStamp: '1234567891', // 不同的时间戳
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'signature'
- }
- const hash1 = generatePaymentParamsHash(params1)
- const hash2 = generatePaymentParamsHash(params2)
- expect(hash1).not.toBe(hash2)
- })
- })
- describe('verifyPaymentParamsIntegrity', () => {
- it('should verify identical parameters', () => {
- const originalParams = {
- timeStamp: '1234567890',
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'signature'
- }
- const receivedParams = { ...originalParams }
- const result = verifyPaymentParamsIntegrity(originalParams, receivedParams)
- expect(result.valid).toBe(true)
- })
- it('should detect tampered parameters', () => {
- const originalParams = {
- timeStamp: '1234567890',
- nonceStr: 'abcdefghijklmnopqrstuvwxyz',
- package: 'prepay_id=wx1234567890',
- signType: 'RSA',
- paySign: 'signature'
- }
- const tamperedParams = {
- ...originalParams,
- package: 'prepay_id=wx9876543210' // 被篡改的预支付ID
- }
- const result = verifyPaymentParamsIntegrity(originalParams, tamperedParams)
- expect(result.valid).toBe(false)
- expect(result.reason).toContain('支付参数被篡改')
- })
- })
- })
|