Alipay.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. <?php
  2. namespace Overtrue\Socialite\Providers;
  3. use Overtrue\Socialite\Exceptions\InvalidArgumentException;
  4. use Overtrue\Socialite\User;
  5. /**
  6. * @see https://opendocs.alipay.com/open/289/105656
  7. */
  8. class Alipay extends Base
  9. {
  10. public const NAME = 'alipay';
  11. protected string $baseUrl = 'https://openapi.alipay.com/gateway.do';
  12. protected array $scopes = ['auth_user'];
  13. protected string $apiVersion = '1.0';
  14. protected string $signType = 'RSA2';
  15. protected string $postCharset = 'UTF-8';
  16. protected string $format = 'json';
  17. protected function getAuthUrl(): string
  18. {
  19. return $this->buildAuthUrlFromBase('https://openauth.alipay.com/oauth2/publicAppAuthorize.htm');
  20. }
  21. protected function getTokenUrl(): string
  22. {
  23. return $this->baseUrl;
  24. }
  25. /**
  26. * @param string $token
  27. *
  28. * @return array
  29. * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
  30. * @throws \GuzzleHttp\Exception\GuzzleException
  31. */
  32. protected function getUserByToken(string $token): array
  33. {
  34. $params = $this->getPublicFields('alipay.user.info.share');
  35. $params += ['auth_token' => $token];
  36. $params['sign'] = $this->generateSign($params);
  37. $response = $this->getHttpClient()->post(
  38. $this->baseUrl,
  39. [
  40. 'form_params' => $params,
  41. 'headers' => [
  42. "content-type" => "application/x-www-form-urlencoded;charset=utf-8",
  43. ],
  44. ]
  45. );
  46. $response = json_decode($response->getBody()->getContents(), true);
  47. if (!empty($response['error_response']) || empty($response['alipay_user_info_share_response'])) {
  48. throw new \InvalidArgumentException('You have error! ' . \json_encode($response, JSON_UNESCAPED_UNICODE));
  49. }
  50. return $response['alipay_user_info_share_response'];
  51. }
  52. /**
  53. * @param array $user
  54. *
  55. * @return \Overtrue\Socialite\User
  56. */
  57. protected function mapUserToObject(array $user): User
  58. {
  59. return new User(
  60. [
  61. 'id' => $user['user_id'] ?? null,
  62. 'name' => $user['nick_name'] ?? null,
  63. 'avatar' => $user['avatar'] ?? null,
  64. 'email' => $user['email'] ?? null,
  65. ]
  66. );
  67. }
  68. /**
  69. * @param string $code
  70. *
  71. * @return array
  72. * @throws \GuzzleHttp\Exception\GuzzleException
  73. * @throws \Overtrue\Socialite\Exceptions\AuthorizeFailedException
  74. * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
  75. */
  76. public function tokenFromCode(string $code): array
  77. {
  78. $response = $this->getHttpClient()->post(
  79. $this->getTokenUrl(),
  80. [
  81. 'form_params' => $this->getTokenFields($code),
  82. 'headers' => [
  83. "content-type" => "application/x-www-form-urlencoded;charset=utf-8",
  84. ],
  85. ]
  86. );
  87. $response = json_decode($response->getBody()->getContents(), true);
  88. if (!empty($response['error_response'])) {
  89. throw new \InvalidArgumentException('You have error! ' . json_encode($response, JSON_UNESCAPED_UNICODE));
  90. }
  91. return $this->normalizeAccessTokenResponse($response['alipay_system_oauth_token_response']);
  92. }
  93. /**
  94. * @return array
  95. * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
  96. */
  97. protected function getCodeFields(): array
  98. {
  99. if (empty($this->redirectUrl)) {
  100. throw new InvalidArgumentException('Please set same redirect URL like your Alipay Official Admin');
  101. }
  102. $fields = array_merge(
  103. [
  104. 'app_id' => $this->getConfig()->get('client_id') ?? $this->getConfig()->get('app_id'),
  105. 'scope' => implode(',', $this->scopes),
  106. 'redirect_uri' => $this->redirectUrl,
  107. ],
  108. $this->parameters
  109. );
  110. return $fields;
  111. }
  112. /**
  113. * @param string $code
  114. *
  115. * @return array|string[]
  116. * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
  117. */
  118. protected function getTokenFields(string $code): array
  119. {
  120. $params = $this->getPublicFields('alipay.system.oauth.token');
  121. $params += [
  122. 'code' => $code,
  123. 'grant_type' => 'authorization_code',
  124. ];
  125. $params['sign'] = $this->generateSign($params);
  126. return $params;
  127. }
  128. /**
  129. * @param string $method
  130. *
  131. * @return array
  132. */
  133. public function getPublicFields(string $method): array
  134. {
  135. return [
  136. 'app_id' => $this->getConfig()->get('client_id') ?? $this->getConfig()->get('app_id'),
  137. 'format' => $this->format,
  138. 'charset' => $this->postCharset,
  139. 'sign_type' => $this->signType,
  140. 'method' => $method,
  141. 'timestamp' => date('Y-m-d H:m:s'),
  142. 'version' => $this->apiVersion,
  143. ];
  144. }
  145. /**
  146. * @param $params
  147. *
  148. * @return string
  149. * @throws InvalidArgumentException
  150. *
  151. * @see https://opendocs.alipay.com/open/289/105656
  152. */
  153. protected function generateSign($params)
  154. {
  155. ksort($params);
  156. $signContent = $this->buildParams($params);
  157. $key = $this->getConfig()->get('rsa_private_key');
  158. $signValue = $this->signWithSHA256RSA($signContent, $key);
  159. return $signValue;
  160. }
  161. /**
  162. * @param string $signContent
  163. * @param string $key
  164. *
  165. * @return string
  166. * @throws \Overtrue\Socialite\Exceptions\InvalidArgumentException
  167. */
  168. protected function signWithSHA256RSA(string $signContent, string $key)
  169. {
  170. if (empty($key)) {
  171. throw new InvalidArgumentException('no RSA private key set.');
  172. }
  173. $key = "-----BEGIN RSA PRIVATE KEY-----\n" .
  174. chunk_split($key, 64, "\n") .
  175. "-----END RSA PRIVATE KEY-----";
  176. openssl_sign($signContent, $signValue, $key, OPENSSL_ALGO_SHA256);
  177. return base64_encode($signValue);
  178. }
  179. /**
  180. * @param array $params
  181. * @param bool $urlencode
  182. * @param array|string[] $except
  183. *
  184. * @return string
  185. */
  186. public static function buildParams(array $params, bool $urlencode = false, array $except = ['sign'])
  187. {
  188. $param_str = '';
  189. foreach ($params as $k => $v) {
  190. if (in_array($k, $except)) {
  191. continue;
  192. }
  193. $param_str .= $k . '=';
  194. $param_str .= $urlencode ? rawurlencode($v) : $v;
  195. $param_str .= '&';
  196. }
  197. return rtrim($param_str, '&');
  198. }
  199. }