-
Notifications
You must be signed in to change notification settings - Fork 248
/
client.go
208 lines (183 loc) · 8.31 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package hms
import (
"crypto/md5"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"sync"
"time"
)
// HMS OAuth url
const tokenURL = "https://oauth-login.cloud.huawei.com/oauth2/v3/token"
// AccessToken expires grace period in seconds.
// The actural ExpiredAt will be substracted with this number to avoid boundray problems.
const accessTokenExpiresGracePeriod = 60
// global variable to store API AccessToken.
// All clients within an instance share one AccessToken grantee scalebility and to avoid rate limit.
var applicationAccessTokens = make(map[[16]byte]ApplicationAccessToken)
// lock when writing to applicationAccessTokens map
var applicationAccessTokensLock sync.Mutex
// ApplicationAccessToken model, received from HMS OAuth API
// https://developer.huawei.com/consumer/en/doc/HMSCore-Guides-V5/open-platform-oauth-0000001050123437-V5#EN-US_TOPIC_0000001050123437__section12493191334711
type ApplicationAccessToken struct {
// App-level access token.
AccessToken string `json:"access_token"`
// Remaining validity period of an access token, in seconds.
ExpiresIn int64 `json:"expires_in"`
// This value is always Bearer, indicating the type of the returned access token.
// TokenType string `json:"token_type"`
// Save the timestamp when AccessToken is obtained
ExpiredAt int64 `json:"-"`
// Request header string
HeaderString string `json:"-"`
}
// Client implements VerifySignature, VerifyOrder and VerifySubscription methods
type Client struct {
clientID string
clientSecret string
clientIDSecretHash [16]byte
httpCli *http.Client
orderSiteURL string // site URL to request order information
subscriptionSiteURL string // site URL to request subscription information
}
// New returns client with credentials.
// Required client_id and client_secret which could be acquired from the HMS API Console.
// When user accountFlag is not equals to 1, orderSiteURL/subscriptionSiteURL are the site URLs that will be used to connect to HMS IAP API services.
// If orderSiteURL or subscriptionSiteURL are not set, default to AppTouch Germany site.
//
// Please refer https://developer.huawei.com/consumer/en/doc/start/api-console-guide
// and https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/api-common-statement-0000001050986127-V5 for details.
func New(clientID, clientSecret, orderSiteURL, subscriptionSiteURL string) *Client {
// Set default order / subscription iap site to AppTouch Germany if it is not provided
if !strings.HasPrefix(orderSiteURL, "http") {
orderSiteURL = "https://orders-at-dre.iap.dbankcloud.com"
}
if !strings.HasPrefix(subscriptionSiteURL, "http") {
subscriptionSiteURL = "https://subscr-at-dre.iap.dbankcloud.com"
}
// Create http client
return &Client{
clientID: clientID,
clientSecret: clientSecret,
clientIDSecretHash: md5.Sum([]byte(clientID + clientSecret)),
httpCli: &http.Client{
Timeout: 10 * time.Second,
},
orderSiteURL: orderSiteURL,
subscriptionSiteURL: subscriptionSiteURL,
}
}
// GetApplicationAccessTokenHeader obtain OAuth AccessToken from HMS
//
// Source code originated from https://github.com/HMS-Core/hms-iap-serverdemo/blob/92241f97fed1b68ddeb7cb37ea4ca6e6d33d2a87/demo/atdemo.go#L37
func (c *Client) GetApplicationAccessTokenHeader() (string, error) {
// To complie with the rate limit (1000/5min as of July 24th, 2020)
// new AccessTokens are requested only when it is expired.
// Please refer https://developer.huawei.com/consumer/en/doc/HMSCore-Guides-V5/open-platform-oauth-0000001050123437-V5 for detailes
if applicationAccessTokens[c.clientIDSecretHash].ExpiredAt > time.Now().Unix() {
return applicationAccessTokens[c.clientIDSecretHash].HeaderString, nil
}
urlValue := url.Values{"grant_type": {"client_credentials"}, "client_secret": {c.clientSecret}, "client_id": {c.clientID}}
resp, err := c.httpCli.PostForm(tokenURL, urlValue)
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var atResponse ApplicationAccessToken
err = json.Unmarshal(bodyBytes, &atResponse)
if err != nil {
return "", err
}
if atResponse.AccessToken != "" {
// update expire time
atResponse.ExpiredAt = atResponse.ExpiresIn + time.Now().Unix() - accessTokenExpiresGracePeriod
// parse request header string
atResponse.HeaderString = fmt.Sprintf(
"Basic %s",
base64.StdEncoding.EncodeToString([]byte(
fmt.Sprintf("APPAT:%s",
atResponse.AccessToken,
),
)),
)
// save AccessToken info to global variable
applicationAccessTokensLock.Lock()
applicationAccessTokens[c.clientIDSecretHash] = atResponse
applicationAccessTokensLock.Unlock()
return atResponse.HeaderString, nil
}
return "", errors.New("Get token fail, " + string(bodyBytes))
}
// Returns root order URL by flag, prefixing with "https://"
func (c *Client) getRootOrderURLByFlag(flag int64) string {
switch flag {
case 1:
return "https://orders-at-dre.iap.dbankcloud.com"
}
return c.orderSiteURL
}
// Returns root subscription URL by flag, prefixing with "https://"
func (c *Client) getRootSubscriptionURLByFlag(flag int64) string {
switch flag {
case 1:
return "https://subscr-at-dre.iap.dbankcloud.com"
}
return c.subscriptionSiteURL
}
// get error based on result code returned from api
func (c *Client) getResponseErrorByCode(code string) error {
switch code {
case "0":
return nil
case "5":
return ErrorResponseInvalidParameter
case "6":
return ErrorResponseCritical
case "8":
return ErrorResponseProductNotBelongToUser
case "9":
return ErrorResponseConsumedProduct
case "11":
return ErrorResponseAbnormalUserAccount
default:
return ErrorResponseUnknown
}
}
// Errors
// ErrorResponseUnknown error placeholder for undocumented errors
var ErrorResponseUnknown error = errors.New("Unknown error from API response")
// ErrorResponseSignatureVerifyFailed failed to verify dataSignature against the response json string.
// https://developer.huawei.com/consumer/en/doc/HMSCore-Guides-V5/verifying-signature-returned-result-0000001050033088-V5
// var ErrorResponseSignatureVerifyFailed error = errors.New("Failed to verify dataSignature against the response json string")
// ErrorResponseInvalidParameter The parameter passed to the API is invalid.
// This error may also indicate that an agreement is not signed or parameters are not set correctly for the in-app purchase settlement in HUAWEI IAP, or the required permission is not in the list.
//
// Check whether the parameter passed to the API is correctly set. If so, check whether required settings in HUAWEI IAP are correctly configured.
// https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/server-error-code-0000001050166248-V5
var ErrorResponseInvalidParameter error = errors.New("The parameter passed to the API is invalid")
// ErrorResponseCritical A critical error occurs during API operations.
//
// Rectify the fault based on the error information in the response. If the fault persists, contact Huawei technical support.
// https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/server-error-code-0000001050166248-V5
var ErrorResponseCritical error = errors.New("A critical error occurs during API operations")
// ErrorResponseProductNotBelongToUser A user failed to consume or confirm a product because the user does not own the product.
//
// https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/server-error-code-0000001050166248-V5
var ErrorResponseProductNotBelongToUser error = errors.New("A user failed to consume or confirm a product because the user does not own the product")
// ErrorResponseConsumedProduct The product cannot be consumed or confirmed because it has been consumed or confirmed.
//
// https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/server-error-code-0000001050166248-V5
var ErrorResponseConsumedProduct error = errors.New("The product cannot be consumed or confirmed because it has been consumed or confirmed")
// ErrorResponseAbnormalUserAccount The user account is abnormal, for example, the user has been deregistered.
//
// https://developer.huawei.com/consumer/en/doc/HMSCore-References-V5/server-error-code-0000001050166248-V5
var ErrorResponseAbnormalUserAccount error = errors.New("The user account is abnormal, for example, the user has been deregistered")