PayUtils.php 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. <?php
  2. namespace App\Services;
  3. use Exception;
  4. class PayUtils
  5. {
  6. /**
  7. * 验证签名
  8. *
  9. * @param array $params 参数数组(包含 sign 和 signType)
  10. * @param string $key 密钥
  11. * @return bool
  12. */
  13. public static function verifySign(array $params, string $key): bool
  14. {
  15. if (!isset($params['sign']) || empty(trim($params['sign']))) {
  16. throw new Exception("Missing 'sign' field in parameters.");
  17. }
  18. if (!isset($params['signType']) || empty(trim($params['signType']))) {
  19. throw new Exception("Missing 'signType' field in parameters.");
  20. }
  21. $sign = strtoupper($params['sign']);
  22. $signType = strtoupper($params['signType']);
  23. // 复制参数并移除 sign 字段
  24. $paramsCopy = $params;
  25. unset($paramsCopy['sign']);
  26. $expectedSign = self::getSign(self::getSignStr($paramsCopy, $key), $signType);
  27. return $sign === $expectedSign;
  28. }
  29. /**
  30. * 对参数进行签名
  31. *
  32. * @param array $params 原始参数
  33. * @param string $key 密钥
  34. * @return array 已签名的参数数组
  35. */
  36. public static function sign(array $params, string $key): array
  37. {
  38. if (!isset($params['signType']) || empty(trim($params['signType']))) {
  39. throw new Exception("Missing 'signType' field in parameters.");
  40. }
  41. $signType = strtoupper($params['signType']);
  42. $signStr = self::getSignStr($params, $key);
  43. $signValue = self::getSign($signStr, $signType);
  44. $params['sign'] = $signValue;
  45. return $params;
  46. }
  47. /**
  48. * 根据签名类型计算签名值
  49. *
  50. * @param string $signStr 待签名字符串
  51. * @param string $signType 签名类型 (MD5/SHA1/SHA256)
  52. * @return string
  53. */
  54. public static function getSign(string $signStr, string $signType): string
  55. {
  56. switch ($signType) {
  57. case 'MD5':
  58. return strtoupper(md5($signStr));
  59. case 'SHA1':
  60. return strtoupper(sha1($signStr));
  61. case 'SHA256':
  62. return strtoupper(hash('sha256', $signStr));
  63. default:
  64. throw new Exception("Unsupported signature type: " . $signType);
  65. }
  66. }
  67. /**
  68. * 构建待签名字符串(排除 sign 字段,并按 key 排序)
  69. *
  70. * @param array $params 参数数组
  71. * @param string $key 密钥
  72. * @return string
  73. */
  74. public static function getSignStr(array $params, string $key): string
  75. {
  76. $sortedParams = self::sortValueRecursively($params);
  77. $items = [];
  78. foreach ($sortedParams as $k => $v) {
  79. if (strtolower($k) === 'sign') {
  80. continue;
  81. }
  82. if ($v === null || (is_string($v) && trim($v) === '')) {
  83. continue;
  84. }
  85. if (is_array($v)) {
  86. $v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
  87. }
  88. $items[] = "{$k}={$v}";
  89. }
  90. $items[] = "key={$key}";
  91. return implode('&', $items);
  92. }
  93. /**
  94. * 递归排序数组或关联数组
  95. *
  96. * @param mixed $value
  97. * @return mixed
  98. */
  99. public static function sortValueRecursively($value)
  100. {
  101. if (is_array($value)) {
  102. ksort($value); // 按键排序
  103. foreach ($value as &$item) {
  104. $item = self::sortValueRecursively($item);
  105. }
  106. }
  107. return $value;
  108. }
  109. }