payment.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /**
  2. * 支付工具函数
  3. * 封装微信支付相关逻辑
  4. */
  5. import Taro from '@tarojs/taro'
  6. /**
  7. * 微信支付参数类型
  8. */
  9. export interface WechatPaymentParams {
  10. timeStamp: string
  11. nonceStr: string
  12. package: string
  13. signType: string
  14. paySign: string
  15. }
  16. /**
  17. * 支付结果类型
  18. */
  19. export interface PaymentResult {
  20. success: boolean
  21. type?: 'success' | 'cancel' | 'fail' | 'error'
  22. message?: string
  23. result?: any
  24. }
  25. /**
  26. * 调用微信支付
  27. * @param paymentData 支付参数
  28. * @returns 支付结果
  29. */
  30. export const requestWechatPayment = async (paymentData: WechatPaymentParams): Promise<PaymentResult> => {
  31. try {
  32. const result = await Taro.requestPayment({
  33. timeStamp: paymentData.timeStamp,
  34. nonceStr: paymentData.nonceStr,
  35. package: paymentData.package,
  36. signType: paymentData.signType as any, // Taro类型定义问题,使用any绕过
  37. paySign: paymentData.paySign
  38. })
  39. return {
  40. success: true,
  41. type: 'success',
  42. result
  43. }
  44. } catch (error: any) {
  45. console.error('微信支付调用失败:', error)
  46. // 根据错误码处理不同场景
  47. if (error.errMsg?.includes('cancel')) {
  48. return {
  49. success: false,
  50. type: 'cancel',
  51. message: '用户取消支付'
  52. }
  53. } else if (error.errMsg?.includes('fail')) {
  54. return {
  55. success: false,
  56. type: 'fail',
  57. message: '支付失败'
  58. }
  59. } else {
  60. return {
  61. success: false,
  62. type: 'error',
  63. message: error.errMsg || '支付异常'
  64. }
  65. }
  66. }
  67. }
  68. /**
  69. * 验证支付参数
  70. * @param paymentData 支付参数
  71. * @returns 验证结果
  72. */
  73. export const validatePaymentParams = (paymentData: WechatPaymentParams): { valid: boolean; errors: string[] } => {
  74. const errors: string[] = []
  75. if (!paymentData.timeStamp) {
  76. errors.push('时间戳不能为空')
  77. }
  78. if (!paymentData.nonceStr) {
  79. errors.push('随机字符串不能为空')
  80. }
  81. if (!paymentData.package) {
  82. errors.push('预支付ID不能为空')
  83. }
  84. if (!paymentData.signType) {
  85. errors.push('签名类型不能为空')
  86. }
  87. if (!paymentData.paySign) {
  88. errors.push('签名不能为空')
  89. }
  90. return {
  91. valid: errors.length === 0,
  92. errors
  93. }
  94. }
  95. /**
  96. * 处理支付金额
  97. * @param amount 金额(元)
  98. * @returns 金额(分)
  99. */
  100. export const formatPaymentAmount = (amount: number): number => {
  101. // 微信支付金额单位为分,需要乘以100
  102. return Math.round(amount * 100)
  103. }
  104. /**
  105. * 检查支付环境
  106. * @returns 是否支持微信支付
  107. */
  108. export const checkPaymentEnvironment = async (): Promise<boolean> => {
  109. try {
  110. // 检查是否在微信小程序环境中
  111. if (typeof Taro.requestPayment === 'undefined') {
  112. console.error('当前环境不支持微信支付')
  113. return false
  114. }
  115. // 可以添加更多环境检查逻辑
  116. return true
  117. } catch (error) {
  118. console.error('检查支付环境失败:', error)
  119. return false
  120. }
  121. }
  122. /**
  123. * 支付安全验证
  124. * @param orderId 订单ID
  125. * @param amount 支付金额
  126. * @param paymentParams 支付参数
  127. * @returns 验证结果
  128. */
  129. export const validatePaymentSecurity = (
  130. orderId: number,
  131. amount: number,
  132. paymentParams: WechatPaymentParams
  133. ): { valid: boolean; reason?: string } => {
  134. // 验证订单ID
  135. if (!orderId || orderId <= 0) {
  136. return { valid: false, reason: '订单ID无效' }
  137. }
  138. // 验证金额
  139. if (!amount || amount <= 0) {
  140. return { valid: false, reason: '支付金额无效' }
  141. }
  142. // 验证支付参数
  143. const paramValidation = validatePaymentParams(paymentParams)
  144. if (!paramValidation.valid) {
  145. return {
  146. valid: false,
  147. reason: `支付参数错误: ${paramValidation.errors.join(', ')}`
  148. }
  149. }
  150. // 时间戳验证(防止重放攻击)
  151. const timestamp = parseInt(paymentParams.timeStamp)
  152. const currentTime = Math.floor(Date.now() / 1000)
  153. const timeDiff = Math.abs(currentTime - timestamp)
  154. // 时间戳应该在5分钟内有效
  155. if (timeDiff > 300) {
  156. return { valid: false, reason: '支付参数已过期,请重新发起支付' }
  157. }
  158. // 随机字符串长度验证 - 放宽限制以适应微信支付实际返回
  159. if (!paymentParams.nonceStr || paymentParams.nonceStr.length < 8) {
  160. return { valid: false, reason: '随机字符串长度无效' }
  161. }
  162. // 签名类型验证 - 放宽限制以适应微信支付实际返回
  163. if (!paymentParams.signType) {
  164. return { valid: false, reason: '签名类型不能为空' }
  165. }
  166. // 验证签名类型是否支持
  167. const supportedSignTypes = ['RSA', 'HMAC-SHA256']
  168. if (!supportedSignTypes.includes(paymentParams.signType)) {
  169. return { valid: false, reason: '签名类型不支持' }
  170. }
  171. // 预支付ID格式验证 - 放宽限制以适应微信支付实际返回
  172. if (!paymentParams.package) {
  173. return { valid: false, reason: '预支付ID不能为空' }
  174. }
  175. // 签名长度验证 - 放宽限制以适应微信支付实际返回
  176. if (!paymentParams.paySign || paymentParams.paySign.length < 16) {
  177. return { valid: false, reason: '签名长度过短' }
  178. }
  179. return { valid: true }
  180. }
  181. /**
  182. * 生成支付参数哈希(用于防篡改验证)
  183. * @param paymentParams 支付参数
  184. * @returns 参数哈希
  185. */
  186. export const generatePaymentParamsHash = (paymentParams: WechatPaymentParams): string => {
  187. const paramsString = [
  188. paymentParams.timeStamp,
  189. paymentParams.nonceStr,
  190. paymentParams.package,
  191. paymentParams.signType
  192. ].join('&')
  193. // 在实际项目中,这里应该使用更安全的哈希算法
  194. // 这里使用简单的哈希作为示例
  195. let hash = 0
  196. for (let i = 0; i < paramsString.length; i++) {
  197. const char = paramsString.charCodeAt(i)
  198. hash = ((hash << 5) - hash) + char
  199. hash = hash & hash // 转换为32位整数
  200. }
  201. return Math.abs(hash).toString(16)
  202. }
  203. /**
  204. * 验证支付参数完整性
  205. * @param originalParams 原始支付参数
  206. * @param receivedParams 接收到的支付参数
  207. * @returns 验证结果
  208. */
  209. export const verifyPaymentParamsIntegrity = (
  210. originalParams: WechatPaymentParams,
  211. receivedParams: WechatPaymentParams
  212. ): { valid: boolean; reason?: string } => {
  213. const originalHash = generatePaymentParamsHash(originalParams)
  214. const receivedHash = generatePaymentParamsHash(receivedParams)
  215. if (originalHash !== receivedHash) {
  216. return { valid: false, reason: '支付参数被篡改' }
  217. }
  218. return { valid: true }
  219. }
  220. /**
  221. * 支付金额一致性验证
  222. * @param expectedAmount 预期金额(元)
  223. * @param paymentAmount 支付金额(分)
  224. * @returns 验证结果
  225. */
  226. export const validateAmountConsistency = (
  227. expectedAmount: number,
  228. paymentAmount: number
  229. ): { valid: boolean; reason?: string } => {
  230. const expectedInFen = Math.round(expectedAmount * 100)
  231. if (expectedInFen !== paymentAmount) {
  232. return {
  233. valid: false,
  234. reason: `金额不一致: 预期 ${expectedInFen} 分,实际 ${paymentAmount} 分`
  235. }
  236. }
  237. return { valid: true }
  238. }
  239. /**
  240. * 支付频率限制检查
  241. */
  242. export class PaymentRateLimiter {
  243. private static instance: PaymentRateLimiter
  244. private attempts: Map<number, number[]> = new Map()
  245. private readonly MAX_ATTEMPTS = 5
  246. private readonly TIME_WINDOW = 60000 // 1分钟
  247. private constructor() {}
  248. static getInstance(): PaymentRateLimiter {
  249. if (!PaymentRateLimiter.instance) {
  250. PaymentRateLimiter.instance = new PaymentRateLimiter()
  251. }
  252. return PaymentRateLimiter.instance
  253. }
  254. /**
  255. * 检查是否超过支付频率限制
  256. */
  257. isRateLimited(orderId: number): { limited: boolean; remainingTime?: number } {
  258. const now = Date.now()
  259. const attempts = this.attempts.get(orderId) || []
  260. // 清理过期的尝试记录
  261. const recentAttempts = attempts.filter(time => now - time < this.TIME_WINDOW)
  262. this.attempts.set(orderId, recentAttempts)
  263. if (recentAttempts.length >= this.MAX_ATTEMPTS) {
  264. const oldestAttempt = Math.min(...recentAttempts)
  265. const remainingTime = this.TIME_WINDOW - (now - oldestAttempt)
  266. return { limited: true, remainingTime }
  267. }
  268. return { limited: false }
  269. }
  270. /**
  271. * 记录支付尝试
  272. */
  273. recordAttempt(orderId: number): void {
  274. const attempts = this.attempts.get(orderId) || []
  275. attempts.push(Date.now())
  276. this.attempts.set(orderId, attempts)
  277. }
  278. /**
  279. * 清除支付尝试记录
  280. */
  281. clearAttempts(orderId: number): void {
  282. this.attempts.delete(orderId)
  283. }
  284. }
  285. /**
  286. * 支付重试逻辑
  287. * @param paymentFn 支付函数
  288. * @param maxRetries 最大重试次数
  289. * @param delay 重试延迟(毫秒)
  290. * @returns 支付结果
  291. */
  292. export const retryPayment = async (
  293. paymentFn: () => Promise<PaymentResult>,
  294. maxRetries: number = 3,
  295. delay: number = 1000
  296. ): Promise<PaymentResult> => {
  297. let lastError: any = null
  298. for (let attempt = 1; attempt <= maxRetries; attempt++) {
  299. try {
  300. const result = await paymentFn()
  301. if (result.success) {
  302. return result
  303. }
  304. // 如果是用户取消,不重试
  305. if (result.type === 'cancel') {
  306. return result
  307. }
  308. lastError = result
  309. if (attempt < maxRetries) {
  310. console.log(`支付失败,第${attempt}次重试...`)
  311. await new Promise(resolve => setTimeout(resolve, delay))
  312. }
  313. } catch (error) {
  314. lastError = error
  315. if (attempt < maxRetries) {
  316. console.log(`支付异常,第${attempt}次重试...`)
  317. await new Promise(resolve => setTimeout(resolve, delay))
  318. }
  319. }
  320. }
  321. return {
  322. success: false,
  323. type: 'error',
  324. message: `支付失败,已重试${maxRetries}次: ${lastError?.message || '未知错误'}`
  325. }
  326. }
  327. /**
  328. * 支付状态枚举
  329. */
  330. export enum PaymentStatus {
  331. PENDING = '待支付',
  332. PROCESSING = '支付中',
  333. SUCCESS = '支付成功',
  334. FAILED = '支付失败',
  335. REFUNDED = '已退款',
  336. CLOSED = '已关闭'
  337. }
  338. /**
  339. * 支付状态管理类
  340. */
  341. export class PaymentStateManager {
  342. private static instance: PaymentStateManager
  343. private state: Map<number, PaymentStatus> = new Map()
  344. private stateHistory: Map<number, { status: PaymentStatus; timestamp: number }[]> = new Map()
  345. private constructor() {}
  346. static getInstance(): PaymentStateManager {
  347. if (!PaymentStateManager.instance) {
  348. PaymentStateManager.instance = new PaymentStateManager()
  349. }
  350. return PaymentStateManager.instance
  351. }
  352. /**
  353. * 设置支付状态
  354. */
  355. setPaymentState(orderId: number, status: PaymentStatus): void {
  356. const previousStatus = this.state.get(orderId)
  357. this.state.set(orderId, status)
  358. // 记录状态历史
  359. const history = this.stateHistory.get(orderId) || []
  360. history.push({
  361. status,
  362. timestamp: Date.now()
  363. })
  364. this.stateHistory.set(orderId, history)
  365. console.log(`订单 ${orderId} 支付状态流转: ${previousStatus || '初始'} -> ${status}`)
  366. }
  367. /**
  368. * 获取支付状态
  369. */
  370. getPaymentState(orderId: number): PaymentStatus | undefined {
  371. return this.state.get(orderId)
  372. }
  373. /**
  374. * 获取支付状态历史
  375. */
  376. getPaymentStateHistory(orderId: number): { status: PaymentStatus; timestamp: number }[] {
  377. return this.stateHistory.get(orderId) || []
  378. }
  379. /**
  380. * 检查是否重复支付
  381. */
  382. isDuplicatePayment(orderId: number): boolean {
  383. const currentStatus = this.getPaymentState(orderId)
  384. return currentStatus === PaymentStatus.PROCESSING || currentStatus === PaymentStatus.SUCCESS
  385. }
  386. /**
  387. * 清除支付状态
  388. */
  389. clearPaymentState(orderId: number): void {
  390. this.state.delete(orderId)
  391. this.stateHistory.delete(orderId)
  392. }
  393. /**
  394. * 获取所有支付状态
  395. */
  396. getAllPaymentStates(): Map<number, PaymentStatus> {
  397. return new Map(this.state)
  398. }
  399. /**
  400. * 验证状态流转是否合法
  401. */
  402. validateStateTransition(orderId: number, newStatus: PaymentStatus): { valid: boolean; reason?: string } {
  403. const currentStatus = this.getPaymentState(orderId)
  404. // 状态流转规则
  405. const allowedTransitions: Record<PaymentStatus, PaymentStatus[]> = {
  406. [PaymentStatus.PENDING]: [PaymentStatus.PROCESSING, PaymentStatus.CLOSED],
  407. [PaymentStatus.PROCESSING]: [PaymentStatus.SUCCESS, PaymentStatus.FAILED, PaymentStatus.CLOSED],
  408. [PaymentStatus.SUCCESS]: [PaymentStatus.REFUNDED],
  409. [PaymentStatus.FAILED]: [PaymentStatus.PROCESSING, PaymentStatus.CLOSED],
  410. [PaymentStatus.REFUNDED]: [],
  411. [PaymentStatus.CLOSED]: []
  412. }
  413. // 初始状态允许任何流转
  414. if (!currentStatus) {
  415. return { valid: true }
  416. }
  417. // 检查是否允许流转
  418. const allowed = allowedTransitions[currentStatus] || []
  419. if (!allowed.includes(newStatus)) {
  420. return {
  421. valid: false,
  422. reason: `状态流转不合法: ${currentStatus} -> ${newStatus}`
  423. }
  424. }
  425. return { valid: true }
  426. }
  427. /**
  428. * 安全设置支付状态(带验证)
  429. */
  430. safeSetPaymentState(orderId: number, status: PaymentStatus): { success: boolean; reason?: string } {
  431. const validation = this.validateStateTransition(orderId, status)
  432. if (!validation.valid) {
  433. return { success: false, reason: validation.reason }
  434. }
  435. this.setPaymentState(orderId, status)
  436. return { success: true }
  437. }
  438. }
  439. /**
  440. * 支付超时处理
  441. * @param orderId 订单ID
  442. * @param timeout 超时时间(毫秒)
  443. * @returns 超时Promise
  444. */
  445. export const createPaymentTimeout = (timeout: number = 30000): Promise<PaymentResult> => {
  446. return new Promise((resolve) => {
  447. setTimeout(() => {
  448. resolve({
  449. success: false,
  450. type: 'error',
  451. message: '支付超时,请检查网络或重试'
  452. })
  453. }, timeout)
  454. })
  455. }
  456. /**
  457. * 支付状态同步
  458. * @param orderId 订单ID
  459. * @param expectedStatus 期望状态
  460. * @param maxAttempts 最大尝试次数
  461. * @param interval 检查间隔(毫秒)
  462. * @returns 同步结果
  463. */
  464. export const syncPaymentStatus = async (
  465. orderId: number,
  466. expectedStatus: PaymentStatus,
  467. maxAttempts: number = 10,
  468. interval: number = 2000
  469. ): Promise<{ synced: boolean; currentStatus?: PaymentStatus }> => {
  470. for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  471. try {
  472. // 这里可以调用后端API查询实际支付状态
  473. // 暂时使用状态管理器模拟
  474. const stateManager = PaymentStateManager.getInstance()
  475. const currentStatus = stateManager.getPaymentState(orderId)
  476. if (currentStatus === expectedStatus) {
  477. return { synced: true, currentStatus }
  478. }
  479. console.log(`支付状态同步中... 第${attempt}次检查,当前状态: ${currentStatus}`)
  480. if (attempt < maxAttempts) {
  481. await new Promise(resolve => setTimeout(resolve, interval))
  482. }
  483. } catch (error) {
  484. console.error(`支付状态同步失败,第${attempt}次尝试:`, error)
  485. }
  486. }
  487. return { synced: false }
  488. }
  489. /**
  490. * 支付状态流转管理器
  491. */
  492. export class PaymentStateFlowManager {
  493. private static instance: PaymentStateFlowManager
  494. private stateManager: PaymentStateManager
  495. private rateLimiter: PaymentRateLimiter
  496. private constructor() {
  497. this.stateManager = PaymentStateManager.getInstance()
  498. this.rateLimiter = PaymentRateLimiter.getInstance()
  499. }
  500. static getInstance(): PaymentStateFlowManager {
  501. if (!PaymentStateFlowManager.instance) {
  502. PaymentStateFlowManager.instance = new PaymentStateFlowManager()
  503. }
  504. return PaymentStateFlowManager.instance
  505. }
  506. /**
  507. * 开始支付流程
  508. */
  509. async startPaymentFlow(orderId: number): Promise<{ success: boolean; reason?: string }> {
  510. // 检查频率限制
  511. const rateLimit = this.rateLimiter.isRateLimited(orderId)
  512. if (rateLimit.limited) {
  513. return {
  514. success: false,
  515. reason: `支付频率过高,请${Math.ceil(rateLimit.remainingTime! / 1000)}秒后重试`
  516. }
  517. }
  518. // 检查是否重复支付
  519. if (this.stateManager.isDuplicatePayment(orderId)) {
  520. return {
  521. success: false,
  522. reason: '订单正在支付中或已支付成功,请勿重复操作'
  523. }
  524. }
  525. // 设置支付中状态
  526. const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.PROCESSING)
  527. if (!setResult.success) {
  528. return setResult
  529. }
  530. // 记录支付尝试
  531. this.rateLimiter.recordAttempt(orderId)
  532. return { success: true }
  533. }
  534. /**
  535. * 处理支付成功
  536. */
  537. handlePaymentSuccess(orderId: number): { success: boolean; reason?: string } {
  538. const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.SUCCESS)
  539. if (setResult.success) {
  540. // 清除频率限制记录
  541. this.rateLimiter.clearAttempts(orderId)
  542. }
  543. return setResult
  544. }
  545. /**
  546. * 处理支付失败
  547. */
  548. handlePaymentFailure(orderId: number): { success: boolean; reason?: string } {
  549. const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.FAILED)
  550. return setResult
  551. }
  552. /**
  553. * 处理支付取消
  554. */
  555. handlePaymentCancel(orderId: number): { success: boolean; reason?: string } {
  556. const setResult = this.stateManager.safeSetPaymentState(orderId, PaymentStatus.CLOSED)
  557. if (setResult.success) {
  558. // 清除频率限制记录
  559. this.rateLimiter.clearAttempts(orderId)
  560. }
  561. return setResult
  562. }
  563. /**
  564. * 处理退款
  565. */
  566. handleRefund(orderId: number): { success: boolean; reason?: string } {
  567. return this.stateManager.safeSetPaymentState(orderId, PaymentStatus.REFUNDED)
  568. }
  569. /**
  570. * 获取支付状态历史
  571. */
  572. getPaymentHistory(orderId: number): { status: PaymentStatus; timestamp: number }[] {
  573. return this.stateManager.getPaymentStateHistory(orderId)
  574. }
  575. /**
  576. * 检查是否可以重试支付
  577. */
  578. canRetryPayment(orderId: number): { canRetry: boolean; reason?: string } {
  579. const currentStatus = this.stateManager.getPaymentState(orderId)
  580. if (currentStatus === PaymentStatus.FAILED) {
  581. return { canRetry: true }
  582. }
  583. if (currentStatus === PaymentStatus.PROCESSING) {
  584. return { canRetry: false, reason: '订单正在支付中,请勿重复操作' }
  585. }
  586. if (currentStatus === PaymentStatus.SUCCESS) {
  587. return { canRetry: false, reason: '订单已支付成功,无需重复支付' }
  588. }
  589. if (currentStatus === PaymentStatus.CLOSED) {
  590. return { canRetry: false, reason: '订单已关闭,无法支付' }
  591. }
  592. return { canRetry: true }
  593. }
  594. /**
  595. * 重置支付状态
  596. */
  597. resetPaymentState(orderId: number): void {
  598. this.stateManager.clearPaymentState(orderId)
  599. this.rateLimiter.clearAttempts(orderId)
  600. }
  601. }