NoPayService.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. namespace App\Services\Payment;
  3. use App\Services\BaseService;
  4. use GuzzleHttp\Client;
  5. use GuzzleHttp\Exception\RequestException;
  6. use Illuminate\Support\Facades\Log;
  7. class NoPayService extends BaseService
  8. {
  9. public const CHANNEL_SCAN = 'NOpay12';
  10. public const CHANNEL_BALANCE = 'NOpay13';
  11. public const WITHDRAW_CHANNEL = 'NOwithdraw';
  12. public const STATE_SUCCESS = '9';
  13. public const STATE_FAIL = '10';
  14. public static function isRechargeChannel(?string $channel): bool
  15. {
  16. return in_array(strtolower((string)$channel), [
  17. strtolower(self::CHANNEL_SCAN),
  18. strtolower(self::CHANNEL_BALANCE),
  19. ], true);
  20. }
  21. public static function isWithdrawChannel(?string $channel): bool
  22. {
  23. return strtolower((string)$channel) === strtolower(self::WITHDRAW_CHANNEL);
  24. }
  25. public static function canUserRecharge($userId): bool
  26. {
  27. $allowedUserIds = array_values(array_filter(array_map(
  28. 'trim',
  29. explode(',', (string)config('app.no_pay_recharge_user_ids', ''))
  30. )));
  31. if (empty($allowedUserIds)) {
  32. return true;
  33. }
  34. return in_array((string)$userId, $allowedUserIds, true);
  35. }
  36. public static function paymentMethod(string $channel): int
  37. {
  38. return strtolower($channel) === strtolower(self::CHANNEL_BALANCE) ? 13 : 12;
  39. }
  40. public static function amount($amount): string
  41. {
  42. return number_format((float)$amount, 2, '.', '');
  43. }
  44. public static function getDepositMerchantId(): string
  45. {
  46. return (string)config('app.no_pay_deposit_mch_id');
  47. }
  48. public static function getWithdrawMerchantId(): string
  49. {
  50. return (string)config('app.no_pay_withdraw_mch_id');
  51. }
  52. public static function getDepositNotifyUrl(): string
  53. {
  54. return rtrim(config('app.url'), '/') . '/api/pay/harvest';
  55. }
  56. public static function getWithdrawNotifyUrl(): string
  57. {
  58. return rtrim(config('app.url'), '/') . '/api/pay/notify';
  59. }
  60. public static function signature(array $params, string $key): string
  61. {
  62. unset($params['sign']);
  63. $params['version'] = $params['version'] ?? 'v1';
  64. $params = array_filter($params, static function ($value) {
  65. return $value !== null && $value !== '';
  66. });
  67. ksort($params, SORT_STRING);
  68. $parts = [];
  69. foreach ($params as $name => $value) {
  70. $parts[] = $name . '=' . $value;
  71. }
  72. $parts[] = 'key=' . $key;
  73. return hash('sha256', implode('&', $parts));
  74. }
  75. public static function pay($amount, string $orderNo, string $memberNo, string $channel): array
  76. {
  77. $data = self::signedData([
  78. 'appId' => self::getDepositMerchantId(),
  79. 'merchantMemberNo' => $memberNo,
  80. 'merchantOrderNo' => $orderNo,
  81. 'amount' => self::amount($amount),
  82. 'paymentMethod' => self::paymentMethod($channel),
  83. 'notifyUrl' => self::getDepositNotifyUrl(),
  84. 'timestamp' => time(),
  85. 'version' => 'v1',
  86. ], (string)config('app.no_pay_deposit_key'));
  87. return self::post((string)config('app.no_pay_deposit_gateway'), $data, self::getDepositMerchantId());
  88. }
  89. public static function queryPayOrder(string $orderNo, string $memberNo): array
  90. {
  91. $data = self::signedData([
  92. 'appId' => self::getDepositMerchantId(),
  93. 'merchantOrderNo' => $orderNo,
  94. 'merchantMemberNo' => $memberNo,
  95. 'timestamp' => time(),
  96. 'version' => 'v1',
  97. ], (string)config('app.no_pay_deposit_key'));
  98. return self::post((string)config('app.no_pay_deposit_query_gateway'), $data, self::getDepositMerchantId());
  99. }
  100. public static function withdraw($amount, string $orderNo, string $memberNo, string $accountName, string $qAccount): array
  101. {
  102. $data = self::signedData([
  103. 'appId' => self::getWithdrawMerchantId(),
  104. 'merchantOrderNo' => $orderNo,
  105. 'merchantMemberNo' => $memberNo,
  106. 'amount' => self::amount($amount),
  107. 'accountName' => $accountName,
  108. 'notifyUrl' => self::getWithdrawNotifyUrl(),
  109. 'qAccount' => $qAccount,
  110. 'timestamp' => time(),
  111. 'version' => 'v1',
  112. ], (string)config('app.no_pay_withdraw_key'));
  113. return self::post((string)config('app.no_pay_withdraw_gateway'), $data, self::getWithdrawMerchantId());
  114. }
  115. public static function verifyDepositNotify(array $params): bool
  116. {
  117. return self::verifyNotify($params, self::getDepositMerchantId(), (string)config('app.no_pay_deposit_key'));
  118. }
  119. public static function verifyWithdrawNotify(array $params): bool
  120. {
  121. return self::verifyNotify($params, self::getWithdrawMerchantId(), (string)config('app.no_pay_withdraw_key'));
  122. }
  123. private static function signedData(array $data, string $key): array
  124. {
  125. $data['sign'] = self::signature($data, $key);
  126. return $data;
  127. }
  128. private static function verifyNotify(array $params, string $merchantId, string $key): bool
  129. {
  130. if ($merchantId === '' || ($params['appId'] ?? '') !== $merchantId || empty($params['sign'])) {
  131. return false;
  132. }
  133. return hash_equals(self::signature($params, $key), strtolower((string)$params['sign']));
  134. }
  135. private static function post(string $url, array $data, string $merchantId): array
  136. {
  137. $logData = $data;
  138. unset($logData['sign'], $logData['password']);
  139. Log::info('NO钱包接口请求', [
  140. 'url' => $url,
  141. 'merchant_id' => $merchantId,
  142. 'data' => $logData,
  143. ]);
  144. try {
  145. $response = (new Client(['timeout' => 10.0]))->post($url, [
  146. 'json' => $data,
  147. 'headers' => [
  148. 'Accept' => 'application/json',
  149. 'appId' => $merchantId,
  150. 'language' => 'zh_CN',
  151. ],
  152. ]);
  153. $body = $response->getBody()->getContents();
  154. Log::info('NO钱包接口响应', [
  155. 'url' => $url,
  156. 'merchant_id' => $merchantId,
  157. 'http_status' => $response->getStatusCode(),
  158. 'body' => $body,
  159. ]);
  160. return json_decode($body, true) ?: [];
  161. } catch (RequestException $e) {
  162. $response = $e->getResponse();
  163. $body = $response ? $response->getBody()->getContents() : '';
  164. Log::error('NO钱包接口请求失败', [
  165. 'url' => $url,
  166. 'merchant_id' => $merchantId,
  167. 'http_status' => $response ? $response->getStatusCode() : null,
  168. 'body' => $body,
  169. 'error' => $e->getMessage(),
  170. ]);
  171. throw $e;
  172. } catch (\Throwable $e) {
  173. Log::error('NO钱包接口异常', [
  174. 'url' => $url,
  175. 'merchant_id' => $merchantId,
  176. 'error' => $e->getMessage(),
  177. ]);
  178. throw $e;
  179. }
  180. }
  181. public static function getWhere(array $search = []): array
  182. {
  183. return [];
  184. }
  185. }