alipay.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package alipayclient
  15. import (
  16. "context"
  17. "crypto"
  18. "crypto/rand"
  19. "crypto/rsa"
  20. "crypto/sha256"
  21. "encoding/base64"
  22. "fmt"
  23. "net/http"
  24. "strings"
  25. "time"
  26. "yunion.io/x/jsonutils"
  27. "yunion.io/x/pkg/errors"
  28. "yunion.io/x/pkg/util/httputils"
  29. "yunion.io/x/onecloud/pkg/util/seclib2"
  30. )
  31. const (
  32. AlipayGatewayUrl = "https://openapi.alipay.com/gateway.do"
  33. AlipayFormat = "json"
  34. AlipayCharset = "UTF-8"
  35. // AlipayCharsetGBK = "GBK"
  36. AlipaySignType = "RSA2"
  37. AlipayVersion = "1.0"
  38. )
  39. type SAlipayClient struct {
  40. url string
  41. appId string
  42. appPrivateKey *rsa.PrivateKey
  43. format string // always json
  44. charset string // always utf-8
  45. alipayPubKey string
  46. signType string // always RSA2
  47. version string
  48. httpClient *http.Client
  49. isDebug bool
  50. }
  51. func NewDefaultAlipayClient(appId string, appPrivateKey string, alipayPubKey string, isDebug bool) (*SAlipayClient, error) {
  52. return NewAlipayClient(AlipayGatewayUrl, appId, appPrivateKey, AlipayFormat, AlipayCharset, alipayPubKey, AlipaySignType, isDebug)
  53. }
  54. func NewAlipayClient(url string, appId string, appPrivateKey string, format string, charset string, alipayPubKey string, signType string, isDebug bool) (*SAlipayClient, error) {
  55. privKey, err := seclib2.DecodePrivateKey([]byte(appPrivateKey))
  56. if err != nil {
  57. return nil, errors.Wrap(err, "Invalid appPrivateKey")
  58. }
  59. httpClient := httputils.GetClient(true, time.Second*15)
  60. httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
  61. return http.ErrUseLastResponse
  62. }
  63. cli := &SAlipayClient{
  64. url: url,
  65. appId: appId,
  66. appPrivateKey: privKey,
  67. format: format,
  68. charset: charset,
  69. alipayPubKey: alipayPubKey,
  70. signType: signType,
  71. version: AlipayVersion,
  72. httpClient: httpClient,
  73. isDebug: isDebug,
  74. }
  75. return cli, nil
  76. }
  77. type SCommonRequestParameters struct {
  78. AppId string `json:"app_id"`
  79. Method string `json:"method"`
  80. Format string `json:"format"`
  81. Charset string `json:"charset"`
  82. SignType string `json:"sign_type"`
  83. Timestamp string `json:"timestamp"`
  84. Version string `json:"version"`
  85. AppAuthToken string `json:"app_auth_token"`
  86. }
  87. func (c *SAlipayClient) getCommonParams(method string) SCommonRequestParameters {
  88. return SCommonRequestParameters{
  89. AppId: c.appId,
  90. Method: method,
  91. Format: c.format,
  92. Charset: c.charset,
  93. SignType: c.signType,
  94. Timestamp: time.Now().Format("2006-01-02 15:04:05"),
  95. Version: c.version,
  96. }
  97. }
  98. func (c *SAlipayClient) execute(ctx context.Context, method string, params map[string]string) (jsonutils.JSONObject, error) {
  99. req := jsonutils.Marshal(params).(*jsonutils.JSONDict)
  100. req.Update(jsonutils.Marshal(c.getCommonParams(method)))
  101. signedReq, err := c.sign(req)
  102. if err != nil {
  103. return nil, errors.Wrap(err, "sign")
  104. }
  105. urlstr := fmt.Sprintf("%s?%s", c.url, signedReq.QueryString())
  106. _, resp, err := httputils.JSONRequest(c.httpClient, ctx, httputils.GET, urlstr, nil, nil, c.isDebug)
  107. if err != nil {
  108. return nil, errors.Wrap(err, "JSONRequest")
  109. }
  110. return resp, nil
  111. }
  112. func signedString(cont *jsonutils.JSONDict) string {
  113. keys := cont.SortedKeys()
  114. segs := make([]string, len(keys))
  115. for i, key := range keys {
  116. val, _ := cont.GetString(key)
  117. segs[i] = fmt.Sprintf("%s=%s", key, val)
  118. }
  119. return strings.Join(segs, "&")
  120. }
  121. func (c *SAlipayClient) sign(request *jsonutils.JSONDict) (jsonutils.JSONObject, error) {
  122. content := request.CopyExcludes("sign")
  123. s := sha256.New()
  124. _, err := s.Write([]byte(signedString(content)))
  125. if err != nil {
  126. return nil, errors.Wrap(err, "sha256.Write")
  127. }
  128. signByte, err := c.appPrivateKey.Sign(rand.Reader, s.Sum(nil), crypto.SHA256)
  129. if err != nil {
  130. return nil, errors.Wrap(err, "privateKey.Sign")
  131. }
  132. signStr := base64.StdEncoding.EncodeToString(signByte)
  133. content.Set("sign", jsonutils.NewString(signStr))
  134. return content, nil
  135. }
  136. type SAlipaySystemOAuthTokenResponse struct {
  137. AccessToken string `json:"access_token"`
  138. AlipayUserId string `json:"alipay_user_id"`
  139. ExpiresIn int `json:"expires_in"`
  140. ReExpiresIn int `json:"re_expires_in"`
  141. RefreshToken string `json:"refresh_token"`
  142. UserId string `json:"user_id"`
  143. }
  144. // {"alipay_system_oauth_token_response":{"access_token":"authusrB9ee9ecc8105e4fc4869b41e8a470dX90","alipay_user_id":"20881023391875409149385062614990","expires_in":1296000,
  145. //
  146. // "re_expires_in":2592000,"refresh_token":"authusrBe1a9c0bdf8d344e786ee57f2df9d6E90","user_id":"2088002723447908"},
  147. // "sign":"rXGE/YX12UrmkEae9jw9WD7B2dS13Hs0r+EnqWwKdERGsUiFmP..."}
  148. func (c *SAlipayClient) GetOAuthToken(ctx context.Context, code string) (*SAlipaySystemOAuthTokenResponse, error) {
  149. resp, err := c.execute(ctx, "alipay.system.oauth.token", map[string]string{
  150. "code": code,
  151. "grant_type": "authorization_code",
  152. })
  153. if err != nil {
  154. return nil, errors.Wrap(err, "Execute")
  155. }
  156. tokenResp := SAlipaySystemOAuthTokenResponse{}
  157. err = resp.Unmarshal(&tokenResp, "alipay_system_oauth_token_response")
  158. if err != nil {
  159. return nil, errors.Wrap(err, "unmarshal fail")
  160. }
  161. return &tokenResp, nil
  162. }
  163. // {"alipay_user_info_share_response":{"code":"10000","msg":"Success","city":"北京市","gender":"m","nick_name":"剑","province":"北京","user_id":"2088002723447908"},
  164. //
  165. // "sign":"WZ+uBloiHvQYOOxq02aS/Y4MEoZf5+ANBnt1OKQ9Z8hOPmQsw=="}
  166. func (c *SAlipayClient) GetUserInfo(ctx context.Context, authToken string) (map[string]string, error) {
  167. resp, err := c.execute(ctx, "alipay.user.info.share", map[string]string{
  168. "auth_token": authToken,
  169. })
  170. if err != nil {
  171. return nil, errors.Wrap(err, "Execute")
  172. }
  173. ret := make(map[string]string)
  174. err = resp.Unmarshal(&ret, "alipay_user_info_share_response")
  175. if err != nil {
  176. return nil, errors.Wrap(err, "Unmarshal map")
  177. }
  178. return ret, nil
  179. }