diff --git a/platformapi/alipayapi/alipayapi.go b/platformapi/alipayapi/alipayapi.go new file mode 100644 index 00000000..8894b165 --- /dev/null +++ b/platformapi/alipayapi/alipayapi.go @@ -0,0 +1,162 @@ +package alipayapi + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "fmt" + "io/ioutil" + "net/http" + "sort" + "strings" + + "git.rosy.net.cn/baseapi" + "git.rosy.net.cn/baseapi/platformapi" + "git.rosy.net.cn/baseapi/utils" +) + +const ( + prodURL = "https://openapi.alipay.com/gateway.do" + + signKey = "sign" +) + +const ( + CommonErrSuccess = "10000" + CommonErrServiceNotAvailable = "20000" +) + +type API struct { + appID string + privateKey *rsa.PrivateKey + client *http.Client + config *platformapi.APIConfig +} + +var ( + commonCanRetryErrMap = map[string]bool{ + CommonErrServiceNotAvailable: true, + } +) + +func New(appID string, privateKey interface{}, config ...*platformapi.APIConfig) (a *API) { + curConfig := platformapi.DefAPIConfig + if len(config) > 0 { + curConfig = *config[0] + } + var keyBytes []byte + if keyBytes2, ok := privateKey.([]byte); ok { + keyBytes = keyBytes2 + } else { + keyBytes, _ = ioutil.ReadFile(privateKey.(string)) + } + pubPem, _ := pem.Decode(keyBytes) + if pubPem == nil { + keyBytes, _ = base64.StdEncoding.DecodeString(string(keyBytes)) + } else { + keyBytes = pubPem.Bytes + } + a = &API{ + appID: appID, + client: &http.Client{Timeout: curConfig.ClientTimeout}, + config: &curConfig, + } + if privateKey, err := x509.ParsePKCS1PrivateKey(keyBytes); err == nil { + a.privateKey = privateKey + } else { + baseapi.SugarLogger.Errorf("alpayapi.New failed with err:%v", err) + } + return a +} + +func (a *API) GetAppID() string { + return a.appID +} + +func (a *API) signParams(params map[string]interface{}) (sign string) { + keys := make([]string, 0) + for k := range params { + if k != signKey { + keys = append(keys, k) + } + } + sort.Strings(keys) + + strList := make([]string, len(keys)) + for index, k := range keys { + strList[index] = fmt.Sprintf("%s=%v", k, params[k]) + } + + finalStr := strings.Join(strList, "&") + // baseapi.SugarLogger.Debugf("finalStr:%s", finalStr) + d := sha256.Sum256([]byte(finalStr)) + signature, _ := rsa.SignPKCS1v15(rand.Reader, a.privateKey, crypto.SHA256, d[:]) + sign = base64.StdEncoding.EncodeToString(signature) + return sign +} + +func method2ResponseKey(method string) (responseKey string) { + responseKey = strings.Join(append(strings.Split(method, "."), "response"), "_") + return responseKey +} + +func (a *API) AccessAPI(method string, params, bizContent map[string]interface{}) (retVal map[string]interface{}, err error) { + params = utils.MergeMaps(map[string]interface{}{ + "app_id": a.GetAppID(), + "method": method, + "format": "JSON", + // "return_url" + "charset": "utf-8", + "sign_type": "RSA2", + "version": "1.0", + }, params) + if len(bizContent) > 0 { + params["biz_content"] = string(utils.MustMarshal(bizContent)) + } + + err = platformapi.AccessPlatformAPIWithRetry(a.client, + func() *http.Request { + params["timestamp"] = utils.GetCurTimeStr() + params[signKey] = a.signParams(params) + fullURL := utils.GenerateGetURL(prodURL, "", params) + request, _ := http.NewRequest(http.MethodGet, fullURL, nil) + // request, _ := http.NewRequest(http.MethodPost, prodURL, bytes.NewReader(utils.MustMarshal(params))) + return request + }, + a.config, + func(response *http.Response, bodyStr string, jsonResult1 map[string]interface{}) (errLevel string, err error) { + if jsonResult1 == nil { + return platformapi.ErrLevelRecoverableErr, fmt.Errorf("mapData is nil") + } + if errorResponse, ok := jsonResult1["error_response"].(map[string]interface{}); ok { + retVal = errorResponse + } else { + retVal, _ = jsonResult1[method2ResponseKey(method)].(map[string]interface{}) + if retVal == nil { + return platformapi.ErrLevelGeneralFail, fmt.Errorf("结果格式异常") + } + } + + errCode := utils.Interface2String(retVal["code"]) + if errCode != CommonErrSuccess && errCode != "" { + err = utils.NewErrorCode(utils.Interface2String(retVal["msg"]), errCode) + if commonCanRetryErrMap[errCode] { + return platformapi.ErrLevelRecoverableErr, err + } + errLevel = platformapi.ErrLevelGeneralFail + } + subErrCode := utils.Interface2String(retVal["sub_code"]) + if subErrCode != "" { + return platformapi.ErrLevelCodeIsNotOK, utils.NewErrorCode(utils.Interface2String(retVal["sub_msg"]), subErrCode) + } else if err != nil { + return errLevel, err + } + + return platformapi.ErrLevelSuccess, nil + }) + return retVal, err +} diff --git a/platformapi/alipayapi/alipayapi_test.go b/platformapi/alipayapi/alipayapi_test.go new file mode 100644 index 00000000..7d787e0e --- /dev/null +++ b/platformapi/alipayapi/alipayapi_test.go @@ -0,0 +1,22 @@ +package alipayapi + +import ( + "git.rosy.net.cn/baseapi" + + "go.uber.org/zap" +) + +var ( + api *API + sugarLogger *zap.SugaredLogger +) + +func init() { + logger, _ := zap.NewDevelopment() + sugarLogger = logger.Sugar() + baseapi.Init(sugarLogger) + + api = New("2019110769024042", []byte(` +MIIEpAIBAAKCAQEAnsbBe0lrK5c4/xhb7ZLDWjGRWmIaj7HyV4LQ9X4EcTV5I5IKLezH1YaNLXytD/VXc5NsJp9IDTFLyOYdXee8lJxAeSQbuBBy1+xLd6qK2JQdVUGP3RZ0pAwVZSc9m0JKj5pYEeA2lvgh4NhSfGEw4BdZacpiDjFWkrQYl+RZkl/eIH2w7sA4wXs/hLSnPiG0VRtLtyYzfGCQdEJNjP5PA6V6CJTd68qTytLnpuaTuVxIYHGGSNd08694b1wOuGpFv6YK+mZkfaGkFoEpp3gUhEQ05duKjNBY71f0ez/Fym7GQYdHNXlsIvCmGQzklkfvQkHj7+MvPpsME4PkqQjRgwIDAQABAoIBAHLzwzDXPtgYbBOEN0oRb43lRS8Cx+gxFRt2goK58c1kwYeXO/dz7loRSDUehs1++wmaOjrcJvYmMpAIykoqdMXDOop6MfdZMUxSr3C78DpNQc9v4BBZKal7diH9/wRhQkolnI2UnvE+RIjdFRsn4pLbVMja1ZMg9WTRLt0JXjAyQZus9pADWADK12OSEIHBvz7/+kiFpq0aM+KPMElQG/mSDg1ESmzfYEbXYmPoiMwU+9frtnprrNdG9h143pb7mdzbXTt/8DbmpFgCfKL7ItpsC3VcZFDsj1Sd3gewrU+FLifH6oPGHTiwEoHNIn5m0RdPVEFoQnQxZnqxzDVBVYECgYEA5zCnBZBLotumxVdIRwncAXKEV5nMHJV2NKVmAc20vHJTsmI4/kA9B2Bjx9jzwwctBzFp1pIadccSbFO48Z5Hn0NdwYOnqba9W50n6R7wO3SnqQuCaoyTAvfkcjrZjZqq88Opa9tKGjD69MFFa+mnK7O7s8B7X6hCa/h80s0zDmkCgYEAr9C5yACb0haAp1WkuYf0B2TyPIofhYOXsjHcJqref+mDHgXSqPntYsXl/RVpJJSAGXJ/CnPd+jHQ0Fis2LuNpZ6ntYzcGoTQtnXx8BdQsnyEjyAvzxWv2JJV12zoSTEW7HL078qqEgbzmort6A8edRiv0kIoNf054QAtv/C9OwsCgYBPATVSlWkDkoR/U8CDZj8kz3miZhB2hC0M+KjPXPiynW80upQ3bsRsTOhMVzuWHlGo7533kZ4xOYJ2OnYtO6XGK0NS6ibVvHkhYadN5yC5cLgK8L/0oW1rykLrNmk6FuzsuKShEyNTqAFauuF6azKRoK44U0LWAa4RL62YbD9SYQKBgQCD0Qp5WXt6WES9MQj/0V607IpxuV1IzRC/GYLlutZ3MKyNpe/7oObKV3XH+nWKZ4xjh+SYAac8Hn1guBtfo77fncQ/6gxcFZgmNOfgCpsGNzVr2cX+jVP6HD0f9xdxSMzXGplp75jzSyL5i5AznKJJSOkJy3A6ilEK0Qd8ERLPYQKBgQC/QSV0c71x3nYz3koOQv7s36i5R0jeHnadSMyGUlrYmn/TaXj0KaUaEYRcgJyWB/dxy3cde8EKlqg0q80Zc6ExkZKhpO6k1lj9Bta+l+KAyVFroYDIy/b0Wukr2qV1mkMK0FQ0X2hVbv6TJBq5RMNeYAy1shOeWIwaS5muSYm4Ig== +`)) +} diff --git a/platformapi/alipayapi/member.go b/platformapi/alipayapi/member.go new file mode 100644 index 00000000..22225db3 --- /dev/null +++ b/platformapi/alipayapi/member.go @@ -0,0 +1,29 @@ +package alipayapi + +import "git.rosy.net.cn/baseapi/utils" + +type UserInfo struct { + Code string `json:"code"` + Msg string `json:"msg"` + Avatar string `json:"avatar"` + City string `json:"city"` + Gender string `json:"gender"` + IsCertified string `json:"is_certified"` + IsStudentCertified string `json:"is_student_certified"` + NickName string `json:"nick_name"` + Province string `json:"province"` + UserID string `json:"user_id"` + UserStatus string `json:"user_status"` + UserType string `json:"user_type"` +} + +func (a *API) UserInfoShare(accessToken string) (userInfo *UserInfo, err error) { + params := map[string]interface{}{ + "auth_token": accessToken, + } + retVal, err := a.AccessAPI("alipay.user.info.share", params, nil) + if err == nil { + err = utils.Map2StructByJson(retVal, &userInfo, false) + } + return userInfo, err +} diff --git a/platformapi/alipayapi/member_test.go b/platformapi/alipayapi/member_test.go new file mode 100644 index 00000000..bfd000d9 --- /dev/null +++ b/platformapi/alipayapi/member_test.go @@ -0,0 +1,11 @@ +package alipayapi + +import ( + "testing" +) + +func TestUserInfoShare(t *testing.T) { + result, err := api.UserInfoShare("authusrBbe61f93632264c44b4e2f5da25267C98") + t.Log(err) + t.Log(result) +} diff --git a/platformapi/alipayapi/utils.go b/platformapi/alipayapi/utils.go new file mode 100644 index 00000000..8f0f7a5a --- /dev/null +++ b/platformapi/alipayapi/utils.go @@ -0,0 +1,49 @@ +package alipayapi + +import "git.rosy.net.cn/baseapi/utils" + +const ( + GrantTypeCode = "authorization_code" + GrantTypeRefresh = "refresh_token" +) + +type TokenInfo struct { + AccessToken string `json:"access_token"` + AlipayUserID string `json:"alipay_user_id"` + ExpiresIn int64 `json:"expires_in"` + ReExpiresIn int64 `json:"re_expires_in"` + RefreshToken string `json:"refresh_token"` + UserID string `json:"user_id"` +} + +func (a *API) UserInfoAuth(scopes []string, state string) (retVal map[string]interface{}, err error) { + retVal, err = a.AccessAPI("alipay.user.info.auth", nil, map[string]interface{}{ + "scopes": scopes, + "state": state, + }) + return retVal, err +} + +func (a *API) AuthTokenAppQuery(appAuthToken string) (retVal map[string]interface{}, err error) { + retVal, err = a.AccessAPI("alipay.open.auth.token.app.query", nil, map[string]interface{}{ + "app_auth_token": appAuthToken, + }) + return retVal, err +} + +func (a *API) SystemAuthToken(grantType, code, refreshToken string) (tokenInfo *TokenInfo, err error) { + params := map[string]interface{}{ + "grant_type": grantType, + } + if code != "" { + params["code"] = code + } + if refreshToken != "" { + params["refresh_token"] = refreshToken + } + retVal, err := a.AccessAPI("alipay.system.oauth.token", params, nil) + if err == nil { + err = utils.Map2StructByJson(retVal, &tokenInfo, false) + } + return tokenInfo, err +} diff --git a/platformapi/alipayapi/utils_test.go b/platformapi/alipayapi/utils_test.go new file mode 100644 index 00000000..f3c39525 --- /dev/null +++ b/platformapi/alipayapi/utils_test.go @@ -0,0 +1,11 @@ +package alipayapi + +import ( + "testing" +) + +func TestSystemAuthToken(t *testing.T) { + result, err := api.SystemAuthToken(GrantTypeCode, "c91ed60bebae473b9507a08e0c86NE98", "") + t.Log(err) + t.Log(result) +}