├── .github └── workflows │ └── tests.yml ├── .gitignore ├── LICENSE ├── README.md ├── alipay ├── alipay.go ├── alipay_test.go ├── app.go ├── app_test.go ├── mini.go ├── mini_experience.go ├── mini_experience_test.go ├── mini_qr_code.go ├── mini_qr_code_test.go ├── mini_template.go ├── mini_template_test.go ├── mini_test.go ├── mini_version.go └── mini_version_test.go └── go.mod /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | name: tests 3 | env: 4 | GO111MODULE: on 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go-version: [1.x, 1.14.x] 11 | platform: [ubuntu-latest, windows-latest] 12 | runs-on: ${{ matrix.platform }} 13 | 14 | steps: 15 | - uses: actions/setup-go@v1 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - uses: actions/checkout@v2 19 | 20 | - name: Cache go modules 21 | uses: actions/cache@v1 22 | with: 23 | path: ~/go/pkg/mod 24 | key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} 25 | restore-keys: ${{ runner.os }}-go- 26 | 27 | - name: Run go fmt 28 | if: runner.os != 'Windows' 29 | run: diff -u <(echo -n) <(gofmt -d -s .) 30 | 31 | - name: Run go vet 32 | run: go vet ./... 33 | 34 | - name: Run go test 35 | run: go test -v -race -coverprofile coverage.txt -covermode atomic ./... 36 | 37 | - name: Upload coverage to Codecov 38 | uses: codecov/codecov-action@v1 39 | with: 40 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cluas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-alipay # 2 | Go 支付宝小程序SDK 3 | 4 | [![GoDoc](https://img.shields.io/static/v1?label=godoc&message=reference&color=blue)](https://pkg.go.dev/github.com/Cluas/go-alipay) 5 | [![GoReport](https://goreportcard.com/badge/github.com/Cluas/go-alipay)](https://goreportcard.com/report/github.com/Cluas/go-alipay) 6 | [![Test Status](https://github.com/Cluas/go-alipay/workflows/tests/badge.svg)](https://github.com/Cluas/go-alipay/actions?query=workflow%3Atests) 7 | [![Test Coverage](https://codecov.io/gh/Cluas/go-alipay/branch/master/graph/badge.svg)](https://codecov.io/gh/Cluas/go-alipay) 8 | 9 | 10 | 支付宝开放平台小程序第三方接口支持 11 | 12 | ### 简单示例 13 | ```go 14 | package main 15 | 16 | import ( 17 | "context" 18 | "crypto/rsa" 19 | "crypto/x509" 20 | "encoding/base64" 21 | "fmt" 22 | 23 | "github.com/Cluas/go-alipay/alipay" 24 | ) 25 | 26 | func main() { 27 | // 小程序代码上架示例 28 | encodedKey, _ := base64.StdEncoding.DecodeString("your_private_key") 29 | privateKey, _ := x509.ParsePKCS1PrivateKey(encodedKey) 30 | publicKey, _ := base64.StdEncoding.DecodeString("your_public_key") 31 | pub, _ := x509.ParsePKIXPublicKey(publicKey) 32 | client := alipay.NewClient(nil, privateKey, pub.(*rsa.PublicKey), alipay.AppID("your_app_id")) 33 | 34 | biz := alipay.OnlineVersionBiz{ 35 | AppVersion: "v0.0.1", 36 | BundleID: "com.alipay.alipaywallet", 37 | } 38 | if err := client.Mini.OnlineVersion(context.Background(), &biz); err != nil { 39 | fmt.Printf("支付宝小程序代码上架失败: %s", err) 40 | } 41 | // 如果是第三方代开发 42 | // client.Mini.OnlineVersion(context.Background(), &biz, alipay.AppAuthToken(token)) 43 | 44 | } 45 | ``` 46 | ### 支持所有已公布的小程序API 47 | 文档地址: https://opendocs.alipay.com/apis/api_49/ 48 | 49 | 50 | -------------------------------------------------------------------------------- /alipay/alipay.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "crypto" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | "encoding/base64" 10 | "encoding/json" 11 | "errors" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "mime/multipart" 16 | "net/http" 17 | "net/url" 18 | "sort" 19 | "strings" 20 | "sync" 21 | "time" 22 | ) 23 | 24 | const ( 25 | defaultBaseURL = "https://openapi.alipay.com/gateway.do" 26 | userAgent = "go-alipay" 27 | 28 | timeLayout = "2006-01-02 15:04:05" 29 | ) 30 | 31 | // Options 公共请求参数 32 | type Options struct { 33 | AppID string // 支付宝分配给开发者的应用ID 34 | Format string // 仅支持JSON 35 | Charset string // 请求使用的编码格式,如utf-8,gbk,gb2312等 36 | SignType string // 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 37 | Version string // 调用的接口版本,固定为:1.0 38 | BizContent string // 请求参数的集合 39 | } 40 | 41 | // Option 参数配置方法 42 | type Option func(*Options) 43 | 44 | // AppID 支付宝分配给开发者的应用ID 45 | func AppID(appID string) Option { 46 | return func(o *Options) { 47 | o.AppID = appID 48 | } 49 | } 50 | 51 | // ValueOptions 可选配置参数 52 | type ValueOptions func(values url.Values) 53 | 54 | // NotifyURL 支付宝服务器主动通知商户服务器里指定的页面http/https路径 55 | func NotifyURL(notifyURL string) ValueOptions { 56 | return func(v url.Values) { 57 | v.Set("notify_url", notifyURL) 58 | } 59 | } 60 | 61 | // AuthToken 针对用户授权接口,获取用户相关数据时,用于标识用户授权关系 62 | func AuthToken(authToken string) ValueOptions { 63 | return func(v url.Values) { 64 | v.Set("auth_token", authToken) 65 | } 66 | } 67 | 68 | // AppAuthToken 第三方应用授权 69 | func AppAuthToken(AppAuthToken string) ValueOptions { 70 | return func(o url.Values) { 71 | o.Set("app_auth_token", AppAuthToken) 72 | } 73 | } 74 | 75 | // Format 仅支持JSON 76 | func Format(format string) Option { 77 | return func(o *Options) { 78 | o.Format = format 79 | } 80 | } 81 | 82 | // Charset 请求使用的编码格式,如utf-8,gbk,gb2312等 83 | func Charset(charset string) Option { 84 | return func(o *Options) { 85 | o.Charset = charset 86 | } 87 | } 88 | 89 | // SignType 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA 90 | func SignType(signType string) Option { 91 | return func(o *Options) { 92 | o.SignType = signType 93 | } 94 | } 95 | 96 | // A Client manages communication with the Alipay API. 97 | type Client struct { 98 | clientMu sync.Mutex // clientMu protects the client during calls that modify the CheckRedirect func. 99 | client *http.Client // HTTP client used to communicate with the API. 100 | 101 | // Base URL for API requests. Defaults to the public Alipay API, but can be 102 | // set to a domain endpoint to use with GitHub Enterprise. BaseURL should 103 | // always be specified with a trailing slash. 104 | BaseURL *url.URL 105 | 106 | o *Options 107 | 108 | PrivateKey *rsa.PrivateKey 109 | PublicKey *rsa.PublicKey 110 | 111 | // User agent used when communicating with the Alipay API. 112 | UserAgent string 113 | 114 | common service // Reuse a single struct instead of allocating one for each service on the heap. 115 | 116 | App *AppService 117 | Mini *MiniService 118 | } 119 | 120 | type service struct { 121 | client *Client 122 | } 123 | 124 | // NewClient returns a new Alipay API client. If a nil httpClient is 125 | // provided, a new http.Client will be used. To use API methods which require 126 | // authentication, provide an http.Client that will perform the authentication 127 | // for you (such as that provided by the golang.org/x/oauth2 library). 128 | func NewClient(httpClient *http.Client, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, setters ...Option) *Client { 129 | if httpClient == nil { 130 | httpClient = &http.Client{} 131 | } 132 | options := &Options{ 133 | Format: "JSON", 134 | Charset: "utf-8", 135 | SignType: "RSA2", 136 | Version: "1.0", 137 | } 138 | for _, setter := range setters { 139 | setter(options) 140 | } 141 | baseURL, _ := url.Parse(defaultBaseURL) 142 | c := &Client{ 143 | client: httpClient, 144 | PrivateKey: privateKey, 145 | PublicKey: publicKey, 146 | BaseURL: baseURL, 147 | UserAgent: userAgent, 148 | o: options, 149 | } 150 | c.common.client = c 151 | c.App = (*AppService)(&c.common) 152 | c.Mini = (*MiniService)(&c.common) 153 | 154 | return c 155 | } 156 | 157 | // File wrapped file content 158 | type File struct { 159 | Name string 160 | Content io.Reader 161 | } 162 | 163 | // Read proxy Content Read 164 | func (f File) Read(p []byte) (n int, err error) { 165 | return f.Content.Read(p) 166 | } 167 | 168 | // NewRequest creates an API request. A relative URL can be provided in urlStr, 169 | // in which case it is resolved relative to the BaseURL of the Client. 170 | // Relative URLs should always be specified without a preceding slash. If 171 | // specified, the value pointed to by body is JSON encoded and included as the 172 | // request body. 173 | func (c *Client) NewRequest(method string, bizContent interface{}, setters ...ValueOptions) (*http.Request, error) { 174 | var ( 175 | sign string 176 | contentType = "application/x-www-form-urlencoded" 177 | buf *bytes.Buffer 178 | req *http.Request 179 | reader io.Reader 180 | err error 181 | ) 182 | v := url.Values{} 183 | v.Set("app_id", c.o.AppID) 184 | v.Set("method", method) 185 | v.Set("format", c.o.Format) 186 | v.Set("charset", c.o.Charset) 187 | v.Set("sign_type", c.o.SignType) 188 | v.Set("timestamp", time.Now().Format(timeLayout)) 189 | v.Set("version", c.o.Version) 190 | for _, setter := range setters { 191 | setter(v) 192 | } 193 | if bizContent != nil { 194 | render, ok := bizContent.(MultiRender) 195 | if ok { 196 | var b bytes.Buffer 197 | w := multipart.NewWriter(&b) 198 | for key, r := range render.MultipartParams() { 199 | var fw io.Writer 200 | if x, ok := r.(*File); ok { 201 | if fw, err = w.CreateFormFile(key, x.Name); err != nil { 202 | return nil, err 203 | } 204 | } 205 | if _, err = io.Copy(fw, r); err != nil { 206 | return nil, err 207 | } 208 | } 209 | params := render.Params() 210 | for key, val := range params { 211 | v.Set(key, val) 212 | } 213 | sign, err = c.Sign(v) 214 | if err != nil { 215 | return nil, err 216 | } 217 | v.Set("sign", sign) 218 | for k := range v { 219 | _ = w.WriteField(k, v.Get(k)) 220 | } 221 | err = w.Close() 222 | if err != nil { 223 | return nil, err 224 | } 225 | reader = &b 226 | contentType = w.FormDataContentType() 227 | } else { 228 | buf = &bytes.Buffer{} 229 | enc := json.NewEncoder(buf) 230 | enc.SetEscapeHTML(false) 231 | err = enc.Encode(bizContent) 232 | if err != nil { 233 | return nil, err 234 | } 235 | v.Set("biz_content", buf.String()) 236 | sign, err = c.Sign(v) 237 | if err != nil { 238 | return nil, err 239 | } 240 | v.Set("sign", sign) 241 | reader = strings.NewReader(v.Encode()) 242 | } 243 | 244 | } else { 245 | sign, err = c.Sign(v) 246 | if err != nil { 247 | return nil, err 248 | } 249 | v.Set("sign", sign) 250 | reader = strings.NewReader(v.Encode()) 251 | } 252 | 253 | req, err = http.NewRequest("POST", c.BaseURL.String(), reader) 254 | if err != nil { 255 | return nil, err 256 | } 257 | v = req.URL.Query() 258 | v.Set("charset", c.o.Charset) 259 | req.URL.RawQuery = v.Encode() 260 | req.Header.Set("Content-Type", contentType) 261 | 262 | if c.UserAgent != "" { 263 | req.Header.Set("User-Agent", c.UserAgent) 264 | } 265 | return req, nil 266 | 267 | } 268 | 269 | // Sign 参数签名 270 | func (c *Client) Sign(values url.Values) (string, error) { 271 | if c.PrivateKey == nil { 272 | return "", nil 273 | } 274 | var buf strings.Builder 275 | keys := make([]string, 0, len(values)) 276 | for k := range values { 277 | keys = append(keys, k) 278 | } 279 | sort.Strings(keys) 280 | for _, k := range keys { 281 | vs := values[k] 282 | for _, v := range vs { 283 | if v == "" { 284 | continue 285 | } 286 | if buf.Len() > 0 { 287 | buf.WriteByte('&') 288 | } 289 | buf.WriteString(k) 290 | buf.WriteByte('=') 291 | buf.WriteString(v) 292 | } 293 | } 294 | valuesStr := buf.String() 295 | 296 | signType := crypto.SHA256 297 | if c.o.SignType == "RSA" { 298 | signType = crypto.SHA1 299 | } 300 | h := crypto.Hash.New(signType) 301 | h.Write([]byte(valuesStr)) 302 | 303 | signature, err := rsa.SignPKCS1v15(rand.Reader, c.PrivateKey, signType, h.Sum(nil)) 304 | if err != nil { 305 | return "", err 306 | } 307 | 308 | sign := base64.StdEncoding.EncodeToString(signature) 309 | 310 | return sign, nil 311 | 312 | } 313 | 314 | // ErrorResponse is common error response. 315 | type ErrorResponse struct { 316 | *http.Response 317 | Code string `json:"code"` 318 | Msg string `json:"msg"` 319 | SubCode string `json:"sub_code,omitempty"` 320 | SubMsg string `json:"sub_msg,omitempty"` 321 | } 322 | 323 | func (r *ErrorResponse) Error() string { 324 | return fmt.Sprintf("%v %v: %d %v %+v, %v %+v", 325 | r.Response.Request.Method, r.Response.Request.URL, 326 | r.Response.StatusCode, r.Msg, r.Code, r.SubCode, r.SubMsg) 327 | } 328 | 329 | func withContext(ctx context.Context, req *http.Request) *http.Request { 330 | return req.WithContext(ctx) 331 | } 332 | 333 | // Response is a Alipay API response. 334 | type Response struct { 335 | *http.Response 336 | } 337 | 338 | // Do sends an API request and returns the API response. The API response is 339 | // JSON decoded and stored in the value pointed to by v, or returned as an 340 | // error if an API error has occurred. If v implements the io.Writer 341 | // interface, the raw response body will be written to v, without attempting to 342 | // first decode it. If rate limit is exceeded and reset time is in the future, 343 | // Do returns *RateLimitError immediately without making a network API call. 344 | // 345 | // The provided ctx must be non-nil, if it is nil an error is returned. If it is canceled or times out, 346 | // ctx.Err() will be returned. 347 | func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Response, error) { 348 | if ctx == nil { 349 | return nil, errors.New("context must be non-nil") 350 | } 351 | req = withContext(ctx, req) 352 | 353 | resp, err := c.client.Do(req) 354 | if err != nil { 355 | // If we got an error, and the context has been canceled, 356 | // the context's error is probably more useful. 357 | select { 358 | case <-ctx.Done(): 359 | return nil, ctx.Err() 360 | default: 361 | } 362 | return nil, err 363 | } 364 | defer resp.Body.Close() 365 | 366 | response := &Response{resp} 367 | 368 | err = c.CheckResponse(resp) 369 | if err != nil { 370 | return response, err 371 | } 372 | 373 | if v != nil { 374 | if w, ok := v.(io.Writer); ok { 375 | io.Copy(w, resp.Body) 376 | } else { 377 | decErr := json.NewDecoder(resp.Body).Decode(v) 378 | if decErr == io.EOF { 379 | decErr = nil // ignore EOF errors caused by empty response body 380 | } 381 | if decErr != nil { 382 | err = decErr 383 | } 384 | } 385 | } 386 | 387 | return response, err 388 | } 389 | 390 | // CheckResponse 检查返回内容 391 | func (c *Client) CheckResponse(r *http.Response) error { 392 | errorResponse := &ErrorResponse{Response: r} 393 | data, err := ioutil.ReadAll(r.Body) 394 | var resp, sign []byte 395 | if err == nil && data != nil { 396 | obj := make(map[string]json.RawMessage) 397 | if err = json.Unmarshal(data, &obj); err != nil { 398 | return err 399 | } 400 | 401 | for k, v := range obj { 402 | if strings.Contains(k, "response") { 403 | resp = v 404 | break 405 | } 406 | } 407 | sign = obj["sign"] 408 | if len(sign) > 0 { 409 | var signStr string 410 | if err = json.Unmarshal(sign, &signStr); err != nil { 411 | return fmt.Errorf("反序列化签名失败: %w", err) 412 | } 413 | if err = c.VerifySign(resp, signStr); err != nil { 414 | return fmt.Errorf("支付宝同步请求签名验证不通过: %w", err) 415 | } 416 | } 417 | if err = json.Unmarshal(resp, &errorResponse); err != nil { 418 | return fmt.Errorf("解析支付宝返回结构失败: %w", err) 419 | } 420 | 421 | } 422 | 423 | if errorResponse.Code == "10000" { 424 | buf := bytes.NewBuffer(resp) 425 | r.Body = ioutil.NopCloser(buf) 426 | return nil 427 | } 428 | return errorResponse 429 | } 430 | 431 | // VerifySign 校验同步请求返回参数 432 | func (c *Client) VerifySign(content []byte, sign string) error { 433 | signData, err := base64.StdEncoding.DecodeString(sign) 434 | if err != nil { 435 | return err 436 | } 437 | 438 | signType := crypto.SHA256 439 | if c.o.SignType == "RSA" { 440 | signType = crypto.SHA1 441 | } 442 | h := crypto.Hash.New(signType) 443 | h.Write(content) 444 | return rsa.VerifyPKCS1v15(c.PublicKey, signType, h.Sum(nil), signData) 445 | } 446 | -------------------------------------------------------------------------------- /alipay/alipay_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "crypto/rsa" 6 | "crypto/x509" 7 | "encoding/base64" 8 | "fmt" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httptest" 12 | "net/url" 13 | "reflect" 14 | "strings" 15 | "testing" 16 | ) 17 | 18 | func setup() (client *Client, mux *http.ServeMux, serverURL string, tearDown func()) { 19 | mux = http.NewServeMux() 20 | server := httptest.NewServer(mux) 21 | client = NewClient(nil, nil, nil) 22 | client.BaseURL, _ = url.Parse(server.URL) 23 | serverURL = server.URL 24 | tearDown = server.Close 25 | return 26 | } 27 | 28 | func TestClient_CheckResponse(t *testing.T) { 29 | res := &http.Response{ 30 | Request: &http.Request{}, 31 | StatusCode: http.StatusOK, 32 | Body: ioutil.NopCloser(strings.NewReader(` 33 | { 34 | "alipay_user_info_share_response": { 35 | "code": "40002", 36 | "msg": "Invalid Arguments", 37 | "sub_code": "isv.invalid-timestamp", 38 | "sub_msg": "非法的时间戳参数" 39 | } 40 | } 41 | `)), 42 | } 43 | client := NewClient(nil, nil, nil) 44 | err := client.CheckResponse(res).(*ErrorResponse) 45 | 46 | if err == nil { 47 | t.Errorf("Expected error response.") 48 | } 49 | 50 | want := &ErrorResponse{ 51 | Response: res, 52 | Msg: "Invalid Arguments", 53 | Code: "40002", 54 | SubCode: "isv.invalid-timestamp", 55 | SubMsg: "非法的时间戳参数", 56 | } 57 | if !reflect.DeepEqual(err, want) { 58 | t.Errorf("Error = %#v, want %#v", err, want) 59 | } 60 | } 61 | 62 | func TestDo(t *testing.T) { 63 | client, mux, _, tearDown := setup() 64 | defer tearDown() 65 | 66 | type foo struct { 67 | A string 68 | } 69 | 70 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 71 | 72 | fmt.Fprint(w, `{ 73 | "foo_response": {"A":"a", "code": "10000"} 74 | }`) 75 | }) 76 | 77 | req, _ := client.NewRequest("foo", nil) 78 | body := new(foo) 79 | client.Do(context.Background(), req, body) 80 | 81 | want := &foo{A: "a"} 82 | if !reflect.DeepEqual(body, want) { 83 | t.Errorf("Response body = %v, want %v", body, want) 84 | } 85 | } 86 | func TestDo_nilContext(t *testing.T) { 87 | client, _, _, tearDown := setup() 88 | defer tearDown() 89 | 90 | req, _ := client.NewRequest(".", nil) 91 | _, err := client.Do(nil, req, nil) 92 | 93 | if !reflect.DeepEqual(err.Error(), "context must be non-nil") { 94 | t.Errorf("Expected context must be non-nil error") 95 | } 96 | } 97 | func TestNewClint(t *testing.T) { 98 | c := NewClient(nil, nil, nil) 99 | if c.BaseURL.String() != defaultBaseURL { 100 | t.Errorf("NewClient BaseURL = %v, want %v", c.BaseURL.String(), defaultBaseURL) 101 | } 102 | if c.UserAgent != userAgent { 103 | t.Errorf("NewClient UserAgent = %v, want %v", c.UserAgent, userAgent) 104 | } 105 | 106 | cNew := NewClient(nil, nil, nil) 107 | if c.client == cNew.client { 108 | t.Error("NewClient returned same http.Client, but they should differ.") 109 | } 110 | 111 | } 112 | 113 | func TestClient_Sign(t *testing.T) { 114 | key := `MIICWwIBAAKBgQC4UcQm06Kz9OH8Q6l2wxSOt9BdObuuC1hJQrQNbkqHU7SM1aI4g156fbAoaEZdb7k2bQSyf6PNWYNS+cl9LPsggbYZ1ZapbqgEt39N4sMKOPUEwMco4P9ZQL6C2+1YfqUc4zZKCqiocgXy0tuV3kKWYleOM/Y+J/2PfAUtKF2p3wIDAQABAoGANAQnRgnNzdla+TUjGvf80jX/oH+NfpWHCc3AQFYSxFQUDPaxPB+exxS3ZP/gc7f23ewwOiuZT3dmf0Es4p2SFOQypacVFyzi4Dj/cvJGxze8Ek047jS5wc6tZiQHjPcmPB0i2/wAJt9ThINdBnSzKrjRhfWy1aRay7fNk1BTmAECQQDvuYRR9yGDifc4T8at2xvUbPKavDFNUx2SNq233A2+DESFa9w3ZirVjiKzLR4/d60Gt/n9j5PssP4syECrGIwBAkEAxNVKNLO44+e8otUPc//s+Uhwzp3ASNT2JkVv4kFO+mkaGErkGnySWmWSbvjziK3TFkYOAGFUzH2+6MPETv+13wJAJKIl/VyVq4NG2z0dsG2+V/z6Kfk+U4GzECf47hLbqsI3KmhsM68SNqZM2TK435wLPe6Zbk0lntMBVJiZgUv0AQJAO/BLgZL9CYHHArro0sUrb5nsqC6HoGYhcvQQJxEGMOESjjU4Ewy+MILfvaVX29Y7AnxgxSLehMsB+LWssPXTdwJAJjRaoDllB2eO5wXAuKZNqYzpI6T3tK7tNG51SDlwkv3WMzuihwkv/tys/pWcFtwJFimbL34e/4dpWB1sHxtA1Q==` 115 | want := "mUZy1ZICyburtoau/gk4VpD50uutt46d0JJoJOB/WRaia0UeWcf6ERwFJmxo+Urf/z410YCcmlX84SuNDzP+erqKzewpWNZyEsYs8QxonZqV1lzy5Yo3NJRKG0Xejp0Zs2Wek96Egg2VeGABmEfsMBx3IoQzKqU3ZeSw/JCPaeA=" 116 | 117 | encodedKey, _ := base64.StdEncoding.DecodeString(key) 118 | privateKey, _ := x509.ParsePKCS1PrivateKey(encodedKey) 119 | 120 | c := NewClient(nil, privateKey, nil, AppID("2016091100484533"), SignType("RSA"), Charset("UTF-8")) 121 | values := url.Values{} 122 | values.Set("method", "alipay.trade.create") 123 | values.Set("app_id", c.o.AppID) 124 | values.Set("sign_type", c.o.SignType) 125 | values.Set("biz_content", `{"out_trade_no":"202004191441122314312","total_amount":"88.88","subject":"iPhone+Xs+Max+256G","buyer_id":"2088102175953034","goods_detail":[],"seller_id":"2088102175107499"}`) 126 | values.Set("charset", c.o.Charset) 127 | values.Set("timestamp", "2020-04-19 14:41:12") 128 | values.Set("version", "1.0") 129 | 130 | got, err := c.Sign(values) 131 | if err != nil { 132 | t.Errorf("Client.Sign returned unexcept err: %v", err) 133 | } 134 | if got != want { 135 | t.Errorf("Client.Sign got %v, want %v", got, want) 136 | } 137 | 138 | } 139 | 140 | func TestClient_VerifySign(t *testing.T) { 141 | publicKey := `MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDI6d306Q8fIfCOaTXyiUeJHkrIvYISRcc73s3vF1ZT7XN8RNPwJxo8pWaJMmvyTn9N4HQ632qJBVHf8sxHi/fEsraprwCtzvzQETrNRwVxLO5jVmRGi60j8Ue1efIlzPXV9je9mkjzOmdssymZkh2QhUrCmZYI/FCEa3/cNMW0QIDAQAB` 142 | sign := `SnAwVaV8YCy3CYiJymlP+kEwxru6uzS4Ym5VDrUZPWgkqnElkq2zRnyigU2k9hXJHdYXZ2JPIyyGx7jufDn4I6IJrTqqhCOC4q254acls9KM/dXjBYYB0QYORG7DqCYXgEz5TGXHkE56gVac4PbqIt/9wnY9o6LBTpfc2fh96+I=` 143 | public, _ := base64.StdEncoding.DecodeString(publicKey) 144 | pub, _ := x509.ParsePKIXPublicKey(public) 145 | 146 | c := NewClient(nil, nil, pub.(*rsa.PublicKey), SignType("RSA")) 147 | err := c.VerifySign([]byte(`{"code":"10000","msg":"Success","avatar":"https:\/\/tfs.alipayobjects.com\/images\/partner\/T15ABtXk8bXXXXXXXX","city":"杭州市","gender":"m","is_certified":"T","is_student_certified":"F","nick_name":"WinWen","province":"浙江省","user_id":"2088912161915762","user_status":"T","user_type":"2"}`), sign) 148 | if err != nil { 149 | t.Errorf("Client.SiVerifySigngn returned unexcept err: %v", err) 150 | } 151 | } 152 | 153 | func TestAppAuthToken(t *testing.T) { 154 | v := url.Values{} 155 | 156 | setter := AppAuthToken("test") 157 | setter(v) 158 | got := v.Get("app_auth_token") 159 | want := "test" 160 | 161 | if got != want { 162 | t.Errorf("AppAuthToken got %v, want %v", got, want) 163 | } 164 | } 165 | 166 | func TestAppID(t *testing.T) { 167 | o := Options{} 168 | 169 | setter := AppID("test") 170 | setter(&o) 171 | got := o.AppID 172 | want := "test" 173 | 174 | if got != want { 175 | t.Errorf("AppID got %v, want %v", got, want) 176 | } 177 | } 178 | 179 | func TestAuthToken(t *testing.T) { 180 | v := url.Values{} 181 | 182 | setter := AuthToken("test") 183 | setter(v) 184 | got := v.Get("auth_token") 185 | want := "test" 186 | 187 | if got != want { 188 | t.Errorf("AppAuthToken got %v, want %v", got, want) 189 | } 190 | } 191 | 192 | func TestCharset(t *testing.T) { 193 | o := Options{} 194 | 195 | setter := Charset("utf-8") 196 | setter(&o) 197 | got := o.Charset 198 | want := "utf-8" 199 | 200 | if got != want { 201 | t.Errorf("Charset got %v, want %v", got, want) 202 | } 203 | } 204 | 205 | func TestFormat(t *testing.T) { 206 | o := Options{} 207 | 208 | setter := Format("JSON") 209 | setter(&o) 210 | got := o.Format 211 | want := "JSON" 212 | 213 | if got != want { 214 | t.Errorf("Charset got %v, want %v", got, want) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /alipay/app.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // AppService 应用服务 8 | // Docs: https://opendocs.alipay.com/apis/api_49 9 | type AppService service 10 | 11 | // CreateAppMemberBiz 应用添加成员 12 | type CreateAppMemberBiz struct { 13 | LogonID string `json:"logon_id"` // 支付宝登录账号ID 14 | Role string `json:"role"` //成员的角色类型,DEVELOPER-开发者,EXPERIENCER-体验者 15 | } 16 | 17 | // CreateMember 应用添加成员,目前只支持小程序类型的应用使用 18 | func (s *AppService) CreateMember(ctx context.Context, biz *CreateAppMemberBiz, opts ...ValueOptions) error { 19 | apiMethod := "alipay.open.app.members.create" 20 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 21 | if err != nil { 22 | return err 23 | } 24 | _, err = s.client.Do(ctx, req, nil) 25 | if err != nil { 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | // DeleteMemberBiz 应用删除成员 32 | type DeleteMemberBiz struct { 33 | UserID string `json:"user_id"` // 蚂蚁统一会员ID 34 | Role string `json:"role"` //成员的角色类型,DEVELOPER-开发者,EXPERIENCER-体验者 35 | } 36 | 37 | // DeleteMember 应用删除成员,目前只支持小程序类型的应用使用 38 | func (s *AppService) DeleteMember(ctx context.Context, biz *DeleteMemberBiz, opts ...ValueOptions) error { 39 | apiMethod := "alipay.open.app.members.delete" 40 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 41 | if err != nil { 42 | return err 43 | } 44 | _, err = s.client.Do(ctx, req, nil) 45 | if err != nil { 46 | return err 47 | } 48 | return nil 49 | } 50 | 51 | // QueryAppMembersBiz 应用查询成员列表 52 | type QueryAppMembersBiz struct { 53 | Role string `json:"role"` //成员的角色类型,DEVELOPER-开发者,EXPERIENCER-体验者 54 | } 55 | 56 | // AppMemberInfo 小程序成员模型 57 | type AppMemberInfo struct { 58 | UserID string `json:"user_id"` 59 | NickName string `json:"nick_name"` 60 | Portrait string `json:"portrait"` 61 | Status string `json:"status"` 62 | GmtJoin string `json:"gmt_join"` 63 | LogonID string `json:"logon_id"` 64 | GmtInvite string `json:"gmt_invite"` 65 | Role string `json:"role"` 66 | } 67 | 68 | // QueryAppMembersResp 成员列表 69 | type QueryAppMembersResp struct { 70 | AppMemberInfoList []*AppMemberInfo `json:"app_member_info_list"` 71 | } 72 | 73 | // QueryAppMembers 应用查询成员列表,目前只支持小程序类型的应用 74 | func (s *AppService) QueryAppMembers(ctx context.Context, biz *QueryAppMembersBiz, opts ...ValueOptions) (*QueryAppMembersResp, error) { 75 | apiMethod := "alipay.open.app.members.query" 76 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 77 | if err != nil { 78 | return nil, err 79 | } 80 | resp := new(QueryAppMembersResp) 81 | _, err = s.client.Do(ctx, req, resp) 82 | if err != nil { 83 | return nil, err 84 | } 85 | return resp, nil 86 | } 87 | 88 | // CreateAppQRCodeResp 生成小程序推广二维码返回值 89 | type CreateAppQRCodeResp struct { 90 | QRCodeURL string `json:"qr_code_url"` 91 | } 92 | 93 | // CreateAppQRCodeBiz 生成小程序推广二维码 94 | type CreateAppQRCodeBiz struct { 95 | URLParam string `json:"url_param"` //小程序中能访问到的页面路径。 96 | QueryParam string `json:"query_param"` //小程序的启动参数,打开小程序的query,在小程序onLaunch的方法中获取。 97 | Describe string `json:"describe"` //对应的二维码描述。 98 | } 99 | 100 | // CreateAppQRCode 生成小程序推广二维码 101 | func (s *AppService) CreateAppQRCode(ctx context.Context, biz *CreateAppQRCodeBiz, opts ...ValueOptions) (*CreateAppQRCodeResp, error) { 102 | apiMethod := "alipay.open.app.qrcode.create" 103 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 104 | if err != nil { 105 | return nil, err 106 | } 107 | resp := new(CreateAppQRCodeResp) 108 | _, err = s.client.Do(ctx, req, resp) 109 | if err != nil { 110 | return nil, err 111 | } 112 | return resp, nil 113 | } 114 | -------------------------------------------------------------------------------- /alipay/app_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestAppService_CreateMember(t *testing.T) { 12 | client, mux, _, tearDown := setup() 13 | defer tearDown() 14 | 15 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 16 | 17 | fmt.Fprint(w, `{ 18 | "alipay_open_app_members_create_response": { 19 | "code": "10000", 20 | "msg": "Success" 21 | } 22 | }`) 23 | }) 24 | 25 | err := client.App.CreateMember(context.Background(), &CreateAppMemberBiz{ 26 | LogonID: "test_id", 27 | Role: "DEVELOPER", 28 | }) 29 | 30 | if err != nil { 31 | t.Errorf("App.CreateMember returned unexcepted error: %v", err) 32 | } 33 | } 34 | 35 | func TestAppService_CreateMember_error(t *testing.T) { 36 | client, mux, _, tearDown := setup() 37 | defer tearDown() 38 | 39 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 40 | 41 | fmt.Fprint(w, `{ 42 | "alipay_open_app_members_create_response": { 43 | "code": "20000", 44 | "msg": "Service Currently Unavailable", 45 | "sub_code": "isp.unknow-error", 46 | "sub_msg": "系统繁忙" 47 | } 48 | }`) 49 | }) 50 | 51 | err := client.App.CreateMember(context.Background(), &CreateAppMemberBiz{ 52 | LogonID: "test_id", 53 | Role: "DEVELOPER", 54 | }) 55 | 56 | if err == nil { 57 | t.Errorf("App.CreateMember excepted error") 58 | } 59 | } 60 | 61 | func TestAppService_DeleteMember(t *testing.T) { 62 | client, mux, _, tearDown := setup() 63 | defer tearDown() 64 | 65 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 66 | 67 | fmt.Fprint(w, `{ 68 | "alipay_open_app_members_delete_response": { 69 | "code": "10000", 70 | "msg": "Success" 71 | } 72 | }`) 73 | }) 74 | 75 | err := client.App.DeleteMember(context.Background(), &DeleteMemberBiz{ 76 | UserID: "test_id", 77 | Role: "DEVELOPER", 78 | }) 79 | if err != nil { 80 | t.Errorf("App.CreateMember returned unexcepted error: %v", err) 81 | } 82 | } 83 | 84 | func TestAppService_DeleteMember_error(t *testing.T) { 85 | client, mux, _, tearDown := setup() 86 | defer tearDown() 87 | 88 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 89 | 90 | fmt.Fprint(w, `{ 91 | "alipay_open_app_members_delete_response": { 92 | "code": "20000", 93 | "msg": "Service Currently Unavailable", 94 | "sub_code": "isp.unknow-error", 95 | "sub_msg": "系统繁忙" 96 | } 97 | }`) 98 | }) 99 | 100 | err := client.App.DeleteMember(context.Background(), &DeleteMemberBiz{ 101 | UserID: "test_id", 102 | Role: "DEVELOPER", 103 | }) 104 | 105 | if err == nil { 106 | t.Errorf("App.DeleteMember excepted error") 107 | } 108 | } 109 | 110 | func TestAppService_QueryAppMembers(t *testing.T) { 111 | client, mux, _, tearDown := setup() 112 | defer tearDown() 113 | 114 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 115 | 116 | fmt.Fprint(w, `{ 117 | "alipay_open_app_members_query_response": { 118 | "code": "10000", 119 | "msg": "Success", 120 | "app_member_info_list": [ 121 | { 122 | "user_id": "20881234123412341234", 123 | "nick_name": "张三", 124 | "portrait": "http://imageg.alipay.com/1", 125 | "status": "VALID", 126 | "gmt_join": "2017-08-12", 127 | "logon_id": "test@e*****e.com", 128 | "gmt_invite": "2017-09-08 12:00:00", 129 | "role": "DEVELOPER" 130 | } 131 | ] 132 | } 133 | }`) 134 | }) 135 | want := &QueryAppMembersResp{AppMemberInfoList: []*AppMemberInfo{ 136 | { 137 | UserID: "20881234123412341234", 138 | NickName: "张三", 139 | Portrait: "http://imageg.alipay.com/1", 140 | Status: "VALID", 141 | GmtJoin: "2017-08-12", 142 | LogonID: "test@e*****e.com", 143 | GmtInvite: "2017-09-08 12:00:00", 144 | Role: "DEVELOPER", 145 | }, 146 | }} 147 | got, err := client.App.QueryAppMembers(context.Background(), &QueryAppMembersBiz{Role: "DEVELOPER"}) 148 | if err != nil { 149 | t.Errorf("App.QueryAppMembers returned unexcepted error: %v", err) 150 | } 151 | if !reflect.DeepEqual(got, want) { 152 | t.Errorf("App.QueryAppMembers got %+v, want %+v", got, want) 153 | } 154 | 155 | } 156 | 157 | func TestAppService_QueryAppMembers_error(t *testing.T) { 158 | client, mux, _, tearDown := setup() 159 | defer tearDown() 160 | 161 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 162 | 163 | fmt.Fprint(w, `{ 164 | "alipay_open_app_members_query_response": { 165 | "code": "20000", 166 | "msg": "Service Currently Unavailable", 167 | "sub_code": "isp.unknow-error", 168 | "sub_msg": "系统繁忙" 169 | } 170 | }`) 171 | }) 172 | 173 | _, err := client.App.QueryAppMembers(context.Background(), &QueryAppMembersBiz{Role: "DEVELOPER"}) 174 | if err == nil { 175 | t.Errorf("App.QueryAppMembers excepted error") 176 | } 177 | } 178 | 179 | func TestAppService_CreateAppQRCode(t *testing.T) { 180 | client, mux, _, tearDown := setup() 181 | defer tearDown() 182 | 183 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 184 | 185 | fmt.Fprint(w, `{ 186 | "alipay_open_app_qrcode_create_response": { 187 | "code": "10000", 188 | "msg": "Success", 189 | "qr_code_url": "http://mmtcdp.stable.alipay.net/wsdk/img?fileid=A*lSbPT5i9C1wAAAAAAAAAAABjAQAAAA&t=9005d7f574f30246b89c20c17302115f&bz=mmtcafts&" 190 | } 191 | }`) 192 | }) 193 | want := &CreateAppQRCodeResp{ 194 | QRCodeURL: "http://mmtcdp.stable.alipay.net/wsdk/img?fileid=A*lSbPT5i9C1wAAAAAAAAAAABjAQAAAA&t=9005d7f574f30246b89c20c17302115f&bz=mmtcafts&", 195 | } 196 | got, err := client.App.CreateAppQRCode(context.Background(), &CreateAppQRCodeBiz{ 197 | URLParam: "pages/index/index", 198 | QueryParam: "x=1", 199 | Describe: "test", 200 | }) 201 | if err != nil { 202 | t.Errorf("App.CreateAppQRCode returned unexcepted error: %v", err) 203 | } 204 | if !reflect.DeepEqual(got, want) { 205 | t.Errorf("App.CreateAppQRCode got %+v, want %+v", got, want) 206 | } 207 | 208 | } 209 | 210 | func TestAppService_CreateAppQRCode_error(t *testing.T) { 211 | client, mux, _, tearDown := setup() 212 | defer tearDown() 213 | 214 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 215 | 216 | fmt.Fprint(w, `{ 217 | "alipay_open_app_qrcode_create_response": { 218 | "code": "20000", 219 | "msg": "Service Currently Unavailable", 220 | "sub_code": "isp.unknow-error", 221 | "sub_msg": "系统繁忙" 222 | } 223 | }`) 224 | }) 225 | 226 | _, err := client.App.CreateAppQRCode(context.Background(), &CreateAppQRCodeBiz{ 227 | URLParam: "pages/index/index", 228 | QueryParam: "x=1", 229 | Describe: "test", 230 | }) 231 | if err == nil { 232 | t.Errorf("App.CreateAppQRCode excepted error") 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /alipay/mini.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "io" 6 | ) 7 | 8 | // MiniService 小程序服务 9 | // 10 | // Docs: https://opendocs.alipay.com/apis/api_49 11 | type MiniService service 12 | 13 | // BaseInfo 小程序基础信息 14 | type BaseInfo struct { 15 | AppName string `json:"app_name"` 16 | AppEnglishName string `json:"app_english_name"` 17 | AppSlogan string `json:"app_slogan"` 18 | AppLogo string `json:"app_logo"` 19 | CategoryNames string `json:"category_names"` 20 | AppDesc string `json:"app_desc"` 21 | ServicePhone string `json:"service_phone"` 22 | ServiceEmail string `json:"service_email"` 23 | SafeDomains []string `json:"safe_domains,omitempty"` 24 | PackageNames []string `json:"package_names,omitempty"` 25 | } 26 | 27 | // QueryBaseInfo 查询小程序基础信息 28 | func (s *MiniService) QueryBaseInfo(ctx context.Context, opts ...ValueOptions) (*BaseInfo, error) { 29 | apiMethod := "alipay.open.mini.baseinfo.query" 30 | req, err := s.client.NewRequest(apiMethod, nil, opts...) 31 | if err != nil { 32 | return nil, err 33 | } 34 | baseInfo := new(BaseInfo) 35 | _, err = s.client.Do(ctx, req, baseInfo) 36 | if err != nil { 37 | return nil, err 38 | } 39 | return baseInfo, nil 40 | } 41 | 42 | // ModifyBaseInfoBiz 小程序修改基础信息 43 | type ModifyBaseInfoBiz struct { 44 | AppName string `json:"app_name,omitempty"` // 小程序应用名称 45 | AppEnglishName string `json:"app_english_name,omitempty"` // 小程序应用英文名称 46 | AppSlogan string `json:"app_slogan,omitempty"` // 小程序应用简介,一句话描述小程序功能 47 | AppLogo *File `json:"app_logo,omitempty"` // 小程序应用logo图标,图片格式必须为:png、jpeg、jpg,建议上传像素为180*180 48 | AppCategoryIDs string `json:"app_category_ids,omitempty"` // 11_12;12_13。小程序类目,格式为 第一个一级类目_第一个二级类目;第二个一级类目_第二个二级类目,详细类目可以参考https://docs.alipay.com/isv/10325 49 | AppDesc string `json:"app_desc,omitempty"` // 小程序应用描述,20-200个字 50 | ServicePhone string `json:"service_phone,omitempty"` // 小程序客服电话 51 | ServiceEmail string `json:"service_email,omitempty"` // 小程序客服邮箱 52 | MiniCategoryIDs string `json:"mini_category_ids,omitempty"` // 新小程序前台类目,一级与二级、三级用下划线隔开,最多可以选四个类目,类目之间;隔开。使用后不再读取app_category_ids值,老前台类目将废弃 53 | } 54 | 55 | type MultiRender interface { 56 | Params() map[string]string 57 | MultipartParams() map[string]io.Reader 58 | } 59 | 60 | func (b *ModifyBaseInfoBiz) Params() map[string]string { 61 | params := make(map[string]string) 62 | if b.AppName != "" { 63 | params["app_name"] = b.AppName 64 | } 65 | if b.AppEnglishName != "" { 66 | params["app_english_name"] = b.AppEnglishName 67 | } 68 | if b.AppSlogan != "" { 69 | params["app_slogan"] = b.AppSlogan 70 | } 71 | if b.AppCategoryIDs != "" { 72 | params["app_category_ids"] = b.AppCategoryIDs 73 | } 74 | if b.AppDesc != "" { 75 | params["app_desc"] = b.AppDesc 76 | } 77 | if b.ServicePhone != "" { 78 | params["service_phone"] = b.ServicePhone 79 | } 80 | if b.ServiceEmail != "" { 81 | params["service_email"] = b.ServiceEmail 82 | } 83 | if b.MiniCategoryIDs != "" { 84 | params["mini_category_ids"] = b.MiniCategoryIDs 85 | } 86 | return params 87 | } 88 | func (b *ModifyBaseInfoBiz) MultipartParams() map[string]io.Reader { 89 | params := make(map[string]io.Reader) 90 | if b.AppLogo != nil { 91 | params["app_logo"] = b.AppLogo 92 | } 93 | return params 94 | } 95 | 96 | // ModifyBaseInfo 小程序修改基础信息 97 | func (s *MiniService) ModifyBaseInfo(ctx context.Context, biz *ModifyBaseInfoBiz, opts ...ValueOptions) error { 98 | apiMethod := "alipay.open.mini.baseinfo.modify" 99 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 100 | if err != nil { 101 | return err 102 | } 103 | _, err = s.client.Do(ctx, req, nil) 104 | if err != nil { 105 | return err 106 | } 107 | return nil 108 | } 109 | 110 | // CreateSafeDomainBiz 小程序添加域白名单 111 | type CreateSafeDomainBiz struct { 112 | SafeDomain string `json:"safe_domain"` // httpRequest域白名单 示例值:example.com 一次只支持设置一个域名 113 | } 114 | 115 | // CreateSafeDomain 小程序添加域白名单 116 | func (s *MiniService) CreateSafeDomain(ctx context.Context, biz *CreateSafeDomainBiz, opts ...ValueOptions) error { 117 | apiMethod := "alipay.open.mini.safedomain.create" 118 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 119 | if err != nil { 120 | return err 121 | } 122 | _, err = s.client.Do(ctx, req, nil) 123 | if err != nil { 124 | return err 125 | } 126 | return nil 127 | } 128 | 129 | // DetectRiskContentBiz 小程序风险内容检测服务biz 130 | type DetectRiskContentBiz struct { 131 | Content string `json:"content"` // 最大长度2000 需要识别的文本,不要包含特殊字符以及双引号等可能引起json格式化错误问题的字符. 132 | 133 | } 134 | 135 | // DetectRiskContentResp 小程序风险内容检测服务resp 136 | type DetectRiskContentResp struct { 137 | Action string `json:"action"` //表示处理结果,REJECTED表示拦截,PASSED表示放过。 138 | Keywords []string `json:"keywords"` // 命中的关键字 139 | UniqueID string `json:"unique_id"` // 业务唯一识别码,可用来对应异步识别结果 140 | } 141 | 142 | // DetectRiskContent 小程序风险内容检测服务 143 | func (s *MiniService) DetectRiskContent(ctx context.Context, biz *DetectRiskContentBiz, opts ...ValueOptions) (*DetectRiskContentResp, error) { 144 | apiMethod := "alipay.security.risk.content.detect" 145 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 146 | if err != nil { 147 | return nil, err 148 | } 149 | resp := new(DetectRiskContentResp) 150 | _, err = s.client.Do(ctx, req, resp) 151 | if err != nil { 152 | return nil, err 153 | } 154 | return resp, nil 155 | 156 | } 157 | 158 | // QueryTinyAppExistBiz 查询是否创建过小程序biz 159 | type QueryTinyAppExistBiz struct { 160 | PID string `json:"pid"` //支付宝账号ID 161 | } 162 | 163 | // QueryTinyAppExistResp 查询是否创建过小程序resp 164 | type QueryTinyAppExistResp struct { 165 | ExistMini string `json:"exist_mini"` // 是否是小程序开发者 166 | } 167 | 168 | // QueryTinyAppExist 查询是否创建过小程序 169 | func (s *MiniService) QueryTinyAppExist(ctx context.Context, biz *QueryTinyAppExistBiz, opts ...ValueOptions) (*QueryTinyAppExistResp, error) { 170 | apiMethod := "alipay.open.mini.tinyapp.exist.query" 171 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 172 | if err != nil { 173 | return nil, err 174 | } 175 | resp := new(QueryTinyAppExistResp) 176 | _, err = s.client.Do(ctx, req, resp) 177 | if err != nil { 178 | return nil, err 179 | } 180 | return resp, nil 181 | 182 | } 183 | 184 | // QueryCategoryBiz 小程序类目树查询 185 | type QueryCategoryBiz struct { 186 | IsFilter bool `json:"is_filter,omitempty"` // 是否需要过滤不可用类目 187 | } 188 | 189 | // MiniAppCategory 小程序类别 190 | type MiniAppCategory struct { 191 | CategoryID string `json:"category_id"` 192 | CategoryName string `json:"category_name"` 193 | ParentCategoryID string `json:"parent_category_id"` 194 | HasChild bool `json:"has_child"` 195 | NeedLicense bool `json:"need_license"` 196 | NeedOutDoorPic bool `json:"need_out_door_pic"` 197 | NeedSpecialLicense bool `json:"need_special_license"` 198 | } 199 | 200 | // QueryCategoryResp 小程序类目树查询resp 201 | type QueryCategoryResp struct { 202 | MiniCategoryList []*MiniAppCategory `json:"mini_category_list"` 203 | CategoryList []*MiniAppCategory `json:"category_list"` 204 | } 205 | 206 | // QueryCategory 小程序类目树查询 207 | func (s *MiniService) QueryCategory(ctx context.Context, biz *QueryCategoryBiz, opts ...ValueOptions) (*QueryCategoryResp, error) { 208 | apiMethod := "alipay.open.mini.category.query" 209 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 210 | if err != nil { 211 | return nil, err 212 | } 213 | resp := new(QueryCategoryResp) 214 | _, err = s.client.Do(ctx, req, resp) 215 | if err != nil { 216 | return nil, err 217 | } 218 | return resp, nil 219 | 220 | } 221 | 222 | // CertifyIndividualBusinessBiz 个人账户升级为个体工商户 223 | type CertifyIndividualBusinessBiz struct { 224 | LiceseNo string `json:"license_no"` // 营业执照 225 | LicesePic string `json:"license_pic"` // 营业执照图片的Base64编码字符串,图片大小不能超过2M 226 | } 227 | 228 | // CertifyIndividualBusinessResp 个人账户升级为个体工商户resp 229 | type CertifyIndividualBusinessResp struct { 230 | CertifyResult bool `json:"certify_result"` // 个体工商户认证结果,true代表认证成功,false代表认证失败 231 | } 232 | 233 | // CertifyIndividualBusiness 个人账户升级为个体工商户 234 | func (s *MiniService) CertifyIndividualBusiness(ctx context.Context, biz *CertifyIndividualBusinessBiz, opts ...ValueOptions) (*CertifyIndividualBusinessResp, error) { 235 | apiMethod := "alipay.open.mini.individual.business.certify" 236 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 237 | if err != nil { 238 | return nil, err 239 | } 240 | resp := new(CertifyIndividualBusinessResp) 241 | _, err = s.client.Do(ctx, req, resp) 242 | if err != nil { 243 | return nil, err 244 | } 245 | return resp, nil 246 | 247 | } 248 | 249 | // SyncContentBiz 小程序内容接入biz 250 | type SyncContentBiz struct { 251 | ContentType string `json:"content_type"` // 内容类型,例如门店、商品等 252 | Operation string `json:"operation"` // 内容类型下的具体操作,比如门店类型下,小程序批量绑定门店。可参考具体内容接入文档中的详细说明。 253 | ContentData string `json:"content_data"` // 具体的内容数据,采用json格式,不同类型不同操作数据不同。可参考具体内容接入文档中的详细说明。 254 | ExtendInfo string `json:"extend_info"` // 扩展信息,json格式。可参考具体内容接入文档中的详细说明。 255 | } 256 | 257 | // SyncContentResp 小程序内容接入resp 258 | type SyncContentResp struct { 259 | ResultData string `json:"result_data"` // 结果数据,json格式,可参考具体内容接入文档中的详细说明。 260 | } 261 | 262 | // SyncContent 小程序内容接入 263 | func (s *MiniService) SyncContent(ctx context.Context, biz *SyncContentBiz, opts ...ValueOptions) (*SyncContentResp, error) { 264 | apiMethod := "alipay.open.mini.content.sync" 265 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 266 | if err != nil { 267 | return nil, err 268 | } 269 | resp := new(SyncContentResp) 270 | _, err = s.client.Do(ctx, req, resp) 271 | if err != nil { 272 | return nil, err 273 | } 274 | return resp, nil 275 | } 276 | -------------------------------------------------------------------------------- /alipay/mini_experience.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import "context" 4 | 5 | // CreateExperienceBiz 小程序生成体验版 6 | type CreateExperienceBiz struct { 7 | AppVersion string `json:"app_version"` //小程序版本号 8 | // 小程序客户端类型,默认为支付宝端。 9 | // 支付宝端:com.alipay.alipaywallet, 10 | // DINGDING端:com.alibaba.android.rimet, 11 | // 高德端:com.amap.app, 12 | // 天猫精灵端:com.alibaba.ailabs.genie.webapps, 13 | // 支付宝IOT:com.alipay.iot.xpaas 14 | BundleID string `json:"bundle_id"` 15 | } 16 | 17 | // CreateExperience 小程序生成体验版 18 | func (s *MiniService) CreateExperience(ctx context.Context, biz *CreateExperienceBiz, opts ...ValueOptions) error { 19 | apiMethod := "alipay.open.mini.experience.create" 20 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 21 | if err != nil { 22 | return err 23 | } 24 | _, err = s.client.Do(ctx, req, nil) 25 | if err != nil { 26 | return err 27 | } 28 | return nil 29 | } 30 | 31 | // QueryExperienceBiz 小程序体验版状态查询 32 | type QueryExperienceBiz struct { 33 | AppVersion string `json:"app_version"` //小程序版本号 34 | // 小程序客户端类型,默认为支付宝端。 35 | // 支付宝端:com.alipay.alipaywallet, 36 | // DINGDING端:com.alibaba.android.rimet, 37 | // 高德端:com.amap.app, 38 | // 天猫精灵端:com.alibaba.ailabs.genie.webapps, 39 | // 支付宝IOT:com.alipay.iot.xpaas 40 | BundleID string `json:"bundle_id"` 41 | } 42 | 43 | // ExperienceStatus 体验版状态 44 | type ExperienceStatus struct { 45 | Status string `json:"status,omitempty"` // 体验版打包状态,expVersionPackaged-体验版打包成功,expVersionPackaging-体验版打包中,notExpVersion-非体验版 46 | ExpQrCodeURL string `json:"exp_qr_code_url,omitempty"` // 小程序体验版二维码地址 47 | } 48 | 49 | // QueryExperience 小程序体验版状态查询 50 | func (s *MiniService) QueryExperience(ctx context.Context, biz *QueryExperienceBiz, opts ...ValueOptions) (*ExperienceStatus, error) { 51 | apiMethod := "alipay.open.mini.experience.query" 52 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 53 | if err != nil { 54 | return nil, err 55 | } 56 | experienceStatus := new(ExperienceStatus) 57 | _, err = s.client.Do(ctx, req, experienceStatus) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return experienceStatus, nil 62 | } 63 | 64 | // CancelExperienceBiz 小程序取消体验版 65 | type CancelExperienceBiz struct { 66 | AppVersion string `json:"app_version"` //小程序版本号 67 | // 小程序客户端类型,默认为支付宝端。 68 | // 支付宝端:com.alipay.alipaywallet, 69 | // DINGDING端:com.alibaba.android.rimet, 70 | // 高德端:com.amap.app, 71 | // 天猫精灵端:com.alibaba.ailabs.genie.webapps, 72 | // 支付宝IOT:com.alipay.iot.xpaas 73 | BundleID string `json:"bundle_id"` 74 | } 75 | 76 | // CancelExperience 小程序取消体验版 77 | func (s *MiniService) CancelExperience(ctx context.Context, biz *CancelExperienceBiz, opts ...ValueOptions) error { 78 | apiMethod := "alipay.open.mini.experience.cancel" 79 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 80 | if err != nil { 81 | return err 82 | } 83 | _, err = s.client.Do(ctx, req, nil) 84 | if err != nil { 85 | return err 86 | } 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /alipay/mini_experience_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestMiniService_CancelExperience(t *testing.T) { 12 | client, mux, _, tearDown := setup() 13 | defer tearDown() 14 | 15 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 16 | 17 | fmt.Fprint(w, `{ 18 | "alipay_open_mini_experience_cancel_response": { 19 | "code": "10000", 20 | "msg": "Success" 21 | } 22 | }`) 23 | }) 24 | 25 | err := client.Mini.CancelExperience(context.Background(), &CancelExperienceBiz{ 26 | AppVersion: "0.0.1", 27 | BundleID: "com.alipay.alipaywallet", 28 | }) 29 | 30 | if err != nil { 31 | t.Errorf("Mini.CancelExperience returned unexcepted error: %v", err) 32 | } 33 | } 34 | 35 | func TestMiniService_CancelExperience_failed(t *testing.T) { 36 | client, mux, _, tearDown := setup() 37 | defer tearDown() 38 | 39 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 40 | 41 | fmt.Fprint(w, `{ 42 | "alipay_open_mini_experience_cancel_response": { 43 | "code": "20000", 44 | "msg": "Service Currently Unavailable", 45 | "sub_code": "isp.unknow-error", 46 | "sub_msg": "系统繁忙" 47 | } 48 | }`) 49 | }) 50 | 51 | err := client.Mini.CancelExperience(context.Background(), &CancelExperienceBiz{ 52 | AppVersion: "0.0.1", 53 | BundleID: "com.alipay.alipaywallet", 54 | }) 55 | 56 | if err == nil { 57 | t.Errorf("Mini.CancelExperience excepted error") 58 | } 59 | } 60 | 61 | func TestMiniService_CreateExperience(t *testing.T) { 62 | client, mux, _, tearDown := setup() 63 | defer tearDown() 64 | 65 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 66 | 67 | fmt.Fprint(w, `{ 68 | "alipay_open_mini_experience_create_response": { 69 | "code": "10000", 70 | "msg": "Success" 71 | } 72 | }`) 73 | }) 74 | err := client.Mini.CreateExperience(context.Background(), &CreateExperienceBiz{ 75 | AppVersion: "0.0.1", 76 | BundleID: "com.alipay.alipaywallet", 77 | }) 78 | 79 | if err != nil { 80 | t.Errorf("Mini.CreateExperience returned unexcepted error: %v", err) 81 | } 82 | } 83 | 84 | func TestMiniService_CreateExperience_failed(t *testing.T) { 85 | client, mux, _, tearDown := setup() 86 | defer tearDown() 87 | 88 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 89 | 90 | fmt.Fprint(w, `{ 91 | "alipay_open_mini_experience_create_response": { 92 | "code": "20000", 93 | "msg": "Service Currently Unavailable", 94 | "sub_code": "isp.unknow-error", 95 | "sub_msg": "系统繁忙" 96 | } 97 | }`) 98 | }) 99 | 100 | err := client.Mini.CreateExperience(context.Background(), &CreateExperienceBiz{ 101 | AppVersion: "0.0.1", 102 | BundleID: "com.alipay.alipaywallet", 103 | }) 104 | 105 | if err == nil { 106 | t.Errorf("Mini.CreateExperience excepted error") 107 | } 108 | } 109 | 110 | func TestMiniService_QueryExperience(t *testing.T) { 111 | client, mux, _, tearDown := setup() 112 | defer tearDown() 113 | 114 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 115 | 116 | fmt.Fprint(w, `{ 117 | "alipay_open_mini_experience_query_response": { 118 | "code": "10000", 119 | "msg": "Success", 120 | "status": "notExpVersion", 121 | "exp_qr_code_url": "https://mobilecodec.alipay.com/show.htm?code=s4x06980mfxeaok1f3zvq8d" 122 | } 123 | }`) 124 | }) 125 | want := &ExperienceStatus{ 126 | Status: "notExpVersion", 127 | ExpQrCodeURL: "https://mobilecodec.alipay.com/show.htm?code=s4x06980mfxeaok1f3zvq8d", 128 | } 129 | got, err := client.Mini.QueryExperience(context.Background(), &QueryExperienceBiz{ 130 | AppVersion: "0.0.1", 131 | BundleID: "com.alipay.alipaywallet", 132 | }) 133 | if err != nil { 134 | t.Errorf("Mini.QueryExperience returned unexcepted error: %v", err) 135 | } 136 | 137 | if !reflect.DeepEqual(got, want) { 138 | t.Errorf("Mini.QueryExperience got %+v, want %+v", got, want) 139 | } 140 | } 141 | 142 | func TestMiniService_QueryExperience_failed(t *testing.T) { 143 | client, mux, _, tearDown := setup() 144 | defer tearDown() 145 | 146 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 147 | 148 | fmt.Fprint(w, `{ 149 | "alipay_open_mini_experience_query_response": { 150 | "code": "20000", 151 | "msg": "Service Currently Unavailable", 152 | "sub_code": "isp.unknow-error", 153 | "sub_msg": "系统繁忙" 154 | } 155 | }`) 156 | }) 157 | 158 | _, err := client.Mini.QueryExperience(context.Background(), &QueryExperienceBiz{ 159 | AppVersion: "0.0.1", 160 | BundleID: "com.alipay.alipaywallet", 161 | }) 162 | 163 | if err == nil { 164 | t.Errorf("Mini.QueryExperience excepted error") 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /alipay/mini_qr_code.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import "context" 4 | 5 | // BindQrCodeBiz 关联普通二维码biz 6 | type BindQrCodeBiz struct { 7 | RouteURL string `json:"route_url"` // 二维码域名,须通过ICP备案验证,支持http、https、ftp开头的链接 8 | //匹配规则,仅支持EXACT(精确匹配)、FUZZY(模糊匹配)两个值。 9 | //精确匹配:根据填写的二维码地址精确匹配,地址完全一致时才能唤起小程序(如:配置二维码地址为https://www.alipay.com/my?id=123,当用户扫这个地址的二维码可唤起小程序)。 10 | //模糊匹配:根据填写的二维码地址模糊匹配,只要地址前缀匹配即可唤起小程序(如:配置二维码地址为https://www.alipay.com/my/,当用户扫的二维码地址为https://www.alipay.com/my/id=123,可唤起小程序)。 11 | Mode string `json:"mode"` 12 | PageRedirection string `json:"page_redirection"` // 小程序功能页,配置扫描二维码后打开的小程序功能页面路径 13 | } 14 | 15 | // BindQrCodeResp 关联普通二维码resp 16 | type BindQrCodeResp struct { 17 | RouteGroup string `json:"route_group"` // 路由规则组,用于唯一标记一条路由规则 18 | } 19 | 20 | // BindQrCode 关联普通二维码 21 | func (s *MiniService) BindQrCode(ctx context.Context, biz *BindQrCodeBiz, opts ...ValueOptions) (*BindQrCodeResp, error) { 22 | apiMethod := "alipay.open.mini.qrcode.bind" 23 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 24 | if err != nil { 25 | return nil, err 26 | } 27 | resp := new(BindQrCodeResp) 28 | _, err = s.client.Do(ctx, req, resp) 29 | if err != nil { 30 | return nil, err 31 | } 32 | return resp, nil 33 | } 34 | 35 | // UnbindQrCodeBiz 删除已关联普通二维码biz 36 | type UnbindQrCodeBiz struct { 37 | RouteGroup string `json:"route_group"` // 路由规则组,用于唯一标记一条路由规则 38 | } 39 | 40 | // UnbindQrCode 删除已关联普通二维码 41 | func (s *MiniService) UnbindQrCode(ctx context.Context, biz *UnbindQrCodeBiz, opts ...ValueOptions) error { 42 | apiMethod := "alipay.open.mini.qrcode.unbind" 43 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 44 | if err != nil { 45 | return err 46 | } 47 | resp := new(BindQrCodeResp) 48 | _, err = s.client.Do(ctx, req, resp) 49 | if err != nil { 50 | return err 51 | } 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /alipay/mini_qr_code_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestMiniService_BindQrCode(t *testing.T) { 12 | client, mux, _, tearDown := setup() 13 | defer tearDown() 14 | 15 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 16 | 17 | fmt.Fprint(w, `{ 18 | "alipay_open_mini_qrcode_bind_response": { 19 | "code": "10000", 20 | "msg": "Success", 21 | "route_group": "78b59c5b6b2946448bc77e17e544b813" 22 | } 23 | }`) 24 | }) 25 | 26 | got, err := client.Mini.BindQrCode(context.Background(), &BindQrCodeBiz{ 27 | RouteURL: "https://www.yoursite.com/", 28 | Mode: "FUZZY", 29 | PageRedirection: "pages/index/index", 30 | }) 31 | if err != nil { 32 | t.Errorf("Mini.BindQrCode returned unexcepted error: %v", err) 33 | } 34 | want := &BindQrCodeResp{ 35 | RouteGroup: "78b59c5b6b2946448bc77e17e544b813", 36 | } 37 | if !reflect.DeepEqual(got, want) { 38 | t.Errorf("Mini.BindQrCode got %+v, want %+v", got, want) 39 | } 40 | } 41 | 42 | func TestMiniService_BindQrCode_failed(t *testing.T) { 43 | client, mux, _, tearDown := setup() 44 | defer tearDown() 45 | 46 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 47 | 48 | fmt.Fprint(w, `{ 49 | "alipay_open_mini_qrcode_bind_response": { 50 | "code": "20000", 51 | "msg": "Service Currently Unavailable", 52 | "sub_code": "isp.unknow-error", 53 | "sub_msg": "系统繁忙" 54 | } 55 | }`) 56 | }) 57 | _, err := client.Mini.BindQrCode(context.Background(), &BindQrCodeBiz{ 58 | RouteURL: "https://www.yoursite.com/", 59 | Mode: "FUZZY", 60 | PageRedirection: "pages/index/index", 61 | }) 62 | if err == nil { 63 | t.Errorf("Mini.BindQrCode excepted error") 64 | } 65 | } 66 | 67 | func TestMiniService_UnbindQrCode(t *testing.T) { 68 | client, mux, _, tearDown := setup() 69 | defer tearDown() 70 | 71 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 72 | 73 | fmt.Fprint(w, `{ 74 | "alipay_open_mini_qrcode_unbind_response": { 75 | "code": "10000", 76 | "msg": "Success" 77 | } 78 | }`) 79 | }) 80 | 81 | err := client.Mini.UnbindQrCode(context.Background(), &UnbindQrCodeBiz{RouteGroup: "78b59c5b6b2946448bc77e17e544b813"}) 82 | if err != nil { 83 | t.Errorf("Mini.UnbindQrCode returned unexcepted error: %v", err) 84 | } 85 | } 86 | 87 | func TestMiniService_UnbindQrCode_failed(t *testing.T) { 88 | client, mux, _, tearDown := setup() 89 | defer tearDown() 90 | 91 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 92 | 93 | fmt.Fprint(w, `{ 94 | "alipay_open_mini_qrcode_unbind_response": { 95 | "code": "20000", 96 | "msg": "Service Currently Unavailable", 97 | "sub_code": "isp.unknow-error", 98 | "sub_msg": "系统繁忙" 99 | } 100 | }`) 101 | }) 102 | err := client.Mini.UnbindQrCode(context.Background(), &UnbindQrCodeBiz{RouteGroup: "78b59c5b6b2946448bc77e17e544b813"}) 103 | if err == nil { 104 | t.Errorf("Mini.UnbindQrCode excepted error") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /alipay/mini_template.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import "context" 4 | 5 | // QueryTemplateUsageBiz 查询使用模板的小程序列表 6 | type QueryTemplateUsageBiz struct { 7 | TemplateID string `json:"template_id"` // 模板id 8 | PageNum int `json:"page_num,omitempty"` // 查询的页数,默认第一页 9 | PageSize int `json:"page_size,omitempty"` // 每页的数量,最多查询50个,默认查询10个 10 | TemplateVersion string `json:"template_version,omitempty"` // 模板小程序的版本号 11 | // 小程序客户端类型,默认为支付宝端。 12 | // 支付宝端:com.alipay.alipaywallet, 13 | // DINGDING端:com.alibaba.android.rimet, 14 | // 高德端:com.amap.app, 15 | // 天猫精灵端:com.alibaba.ailabs.genie.webapps, 16 | // 支付宝IOT:com.alipay.iot.xpaas 17 | BundleID string `json:"bundle_id,omitempty"` 18 | } 19 | 20 | // QueryTemplateUsageResp 查询使用模板的小程序列表resp 21 | type QueryTemplateUsageResp struct { 22 | TemplateUsageInfoList []*TemplateUsageInfo `json:"template_usage_info_list"` // 模板使用信息 23 | } 24 | 25 | // TemplateUsageInfo 小程序信息 26 | type TemplateUsageInfo struct { 27 | MiniAppID string `json:"mini_app_id"` // 商家小程序appId 28 | AppVersion string `json:"app_version"` // 商家小程序版本号 29 | } 30 | 31 | // QueryTemplateUsage 查询使用模板的小程序列表 32 | func (s *MiniService) QueryTemplateUsage(ctx context.Context, biz *QueryTemplateUsageBiz, opts ...ValueOptions) (*QueryTemplateUsageResp, error) { 33 | apiMethod := "alipay.open.mini.template.usage.query" 34 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | resp := new(QueryTemplateUsageResp) 39 | _, err = s.client.Do(ctx, req, resp) 40 | if err != nil { 41 | return nil, err 42 | } 43 | return resp, nil 44 | } 45 | -------------------------------------------------------------------------------- /alipay/mini_template_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/http" 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func TestMiniService_QueryTemplateUsage(t *testing.T) { 12 | client, mux, _, tearDown := setup() 13 | defer tearDown() 14 | 15 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 16 | 17 | fmt.Fprint(w, `{ 18 | "alipay_open_mini_template_usage_query_response": { 19 | "code": "10000", 20 | "msg": "Success", 21 | "template_usage_info_list": [ 22 | { 23 | "mini_app_id": "2018011111111111", 24 | "app_version": "0.0.1" 25 | } 26 | ] 27 | } 28 | }`) 29 | }) 30 | 31 | got, err := client.Mini.QueryTemplateUsage(context.Background(), &QueryTemplateUsageBiz{ 32 | TemplateID: "1", 33 | PageNum: 1, 34 | PageSize: 10, 35 | TemplateVersion: "0.0.1", 36 | BundleID: "com.alipay.alipaywallet", 37 | }) 38 | if err != nil { 39 | t.Errorf("Mini.BindQrCode returned unexcepted error: %v", err) 40 | } 41 | want := &QueryTemplateUsageResp{ 42 | TemplateUsageInfoList: []*TemplateUsageInfo{ 43 | {MiniAppID: "2018011111111111", AppVersion: "0.0.1"}, 44 | }, 45 | } 46 | if !reflect.DeepEqual(got, want) { 47 | t.Errorf("Mini.QueryTemplateUsage got %+v, want %+v", got, want) 48 | } 49 | } 50 | 51 | func TestMiniService_QueryTemplateUsage_failed(t *testing.T) { 52 | client, mux, _, tearDown := setup() 53 | defer tearDown() 54 | 55 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 56 | 57 | fmt.Fprint(w, `{ 58 | "alipay_open_mini_template_usage_query_response": { 59 | "code": "20000", 60 | "msg": "Service Currently Unavailable", 61 | "sub_code": "isp.unknow-error", 62 | "sub_msg": "系统繁忙" 63 | } 64 | }`) 65 | }) 66 | _, err := client.Mini.QueryTemplateUsage(context.Background(), &QueryTemplateUsageBiz{ 67 | TemplateID: "1", 68 | PageNum: 1, 69 | PageSize: 10, 70 | TemplateVersion: "0.0.1", 71 | BundleID: "com.alipay.alipaywallet", 72 | }) 73 | if err == nil { 74 | t.Errorf("Mini.QueryTemplateUsage excepted error") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /alipay/mini_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestMiniService_QueryMiniBaseInfo(t *testing.T) { 13 | client, mux, _, tearDown := setup() 14 | defer tearDown() 15 | 16 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 17 | 18 | fmt.Fprint(w, `{ 19 | "alipay_open_mini_baseinfo_query_response": { 20 | "code": "10000", 21 | "msg": "Success", 22 | "app_name": "小程序示例", 23 | "app_english_name": "demoexample", 24 | "app_slogan": "提供小程序示例功能", 25 | "app_logo": "https://appstoreisvpic.alipayobjects.com/prod/04843e84-f1fd-4717-a230-1c72de99aa5d.png", 26 | "category_names": "航空票务_航空公司;生活服务_室内清洁服务;", 27 | "app_desc": "小程序官方示例Demo,展示已支持的接口能力及组件。", 28 | "service_phone": "13110101010", 29 | "service_email": "example@mail.com", 30 | "safe_domains": [ 31 | "example.com" 32 | ], 33 | "package_names": [ 34 | "小程序基础功能" 35 | ] 36 | } 37 | }`) 38 | }) 39 | 40 | got, err := client.Mini.QueryBaseInfo(context.Background()) 41 | if err != nil { 42 | t.Errorf("Mini.QueryMiniBaseInfo returned unexcepted error: %v", err) 43 | } 44 | want := &BaseInfo{ 45 | AppName: "小程序示例", 46 | AppEnglishName: "demoexample", 47 | AppSlogan: "提供小程序示例功能", 48 | AppLogo: "https://appstoreisvpic.alipayobjects.com/prod/04843e84-f1fd-4717-a230-1c72de99aa5d.png", 49 | CategoryNames: "航空票务_航空公司;生活服务_室内清洁服务;", 50 | AppDesc: "小程序官方示例Demo,展示已支持的接口能力及组件。", 51 | ServicePhone: "13110101010", 52 | ServiceEmail: "example@mail.com", 53 | SafeDomains: []string{"example.com"}, 54 | PackageNames: []string{"小程序基础功能"}, 55 | } 56 | if !reflect.DeepEqual(got, want) { 57 | t.Errorf("Mini.QueryMiniBaseInfo got %+v, want %+v", got, want) 58 | } 59 | } 60 | 61 | func TestMiniService_ModifyBaseInfo(t *testing.T) { 62 | client, mux, _, tearDown := setup() 63 | defer tearDown() 64 | 65 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 66 | 67 | fmt.Fprint(w, `{ 68 | "alipay_open_mini_safedomain_create_response": { 69 | "code": "10000", 70 | "msg": "Success" 71 | } 72 | }`) 73 | }) 74 | 75 | err := client.Mini.ModifyBaseInfo(context.Background(), &ModifyBaseInfoBiz{ 76 | AppName: "小程序demo", 77 | AppEnglishName: "demoexample", 78 | AppSlogan: "这是一个小程序示例", 79 | AppLogo: &File{ 80 | Name: "测试logo", 81 | Content: bytes.NewReader([]byte("测试logo")), 82 | }, 83 | AppCategoryIDs: "11_12;12_13", 84 | AppDesc: "这是一个小程序的描述这是一个小程序的描述这是一个小程序的描述这是一个小程序的描述", 85 | ServicePhone: "13110101010", 86 | ServiceEmail: "example@mail.com", 87 | MiniCategoryIDs: "XS1001_XS2001_XS3002;XS1011_XS2089;XS1002_XS2008_XS3024", 88 | }) 89 | if err != nil { 90 | t.Errorf("Mini.ModifyBaseInfo returned unexcepted error: %v", err) 91 | } 92 | } 93 | 94 | func TestMiniService_ModifyBaseInfo_failed(t *testing.T) { 95 | client, mux, _, tearDown := setup() 96 | defer tearDown() 97 | 98 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 99 | 100 | fmt.Fprint(w, `{ 101 | "alipay_open_mini_baseinfo_modify_response": { 102 | "code": "20000", 103 | "msg": "Service Currently Unavailable", 104 | "sub_code": "isp.unknow-error", 105 | "sub_msg": "系统繁忙" 106 | } 107 | }`) 108 | }) 109 | err := client.Mini.ModifyBaseInfo(context.Background(), &ModifyBaseInfoBiz{ 110 | AppName: "小程序demo", 111 | AppEnglishName: "demoexample", 112 | AppSlogan: "这是一个小程序示例", 113 | AppLogo: &File{ 114 | Name: "测试logo", 115 | Content: bytes.NewReader([]byte("测试logo")), 116 | }, 117 | AppCategoryIDs: "11_12;12_13", 118 | AppDesc: "这是一个小程序的描述这是一个小程序的描述这是一个小程序的描述这是一个小程序的描述", 119 | ServicePhone: "13110101010", 120 | ServiceEmail: "example@mail.com", 121 | MiniCategoryIDs: "XS1001_XS2001_XS3002;XS1011_XS2089;XS1002_XS2008_XS3024", 122 | }) 123 | if err == nil { 124 | t.Errorf("Mini.ModifyBaseInfo excepted error") 125 | } 126 | } 127 | 128 | func TestMiniService_CreateSafeDomain(t *testing.T) { 129 | client, mux, _, tearDown := setup() 130 | defer tearDown() 131 | 132 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 133 | 134 | fmt.Fprint(w, `{ 135 | "alipay_open_mini_safedomain_create_response": { 136 | "code": "10000", 137 | "msg": "Success" 138 | } 139 | }`) 140 | }) 141 | 142 | err := client.Mini.CreateSafeDomain(context.Background(), &CreateSafeDomainBiz{SafeDomain: "example.com"}) 143 | if err != nil { 144 | t.Errorf("Mini.CreateSafeDomain returned unexcepted error: %v", err) 145 | } 146 | } 147 | 148 | func TestMiniService_CreateSafeDomain_failed(t *testing.T) { 149 | client, mux, _, tearDown := setup() 150 | defer tearDown() 151 | 152 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 153 | 154 | fmt.Fprint(w, `{ 155 | "alipay_open_mini_baseinfo_query_response": { 156 | "code": "20000", 157 | "msg": "Service Currently Unavailable", 158 | "sub_code": "isp.unknow-error", 159 | "sub_msg": "系统繁忙" 160 | } 161 | }`) 162 | }) 163 | err := client.Mini.CreateSafeDomain(context.Background(), nil) 164 | if err == nil { 165 | t.Errorf("Mini.CreateSafeDomain excepted error") 166 | } 167 | } 168 | 169 | func TestMiniService_DetectRiskContent(t *testing.T) { 170 | client, mux, _, tearDown := setup() 171 | defer tearDown() 172 | 173 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 174 | 175 | fmt.Fprint(w, `{ 176 | "alipay_security_risk_content_detect_response": { 177 | "code": "10000", 178 | "msg": "Success", 179 | "action": "REJECTED", 180 | "keywords": [ "张三", "李四" ], 181 | "unique_id": "0ba600421493362500440513027526" 182 | } 183 | }`) 184 | }) 185 | 186 | got, err := client.Mini.DetectRiskContent(context.Background(), &DetectRiskContentBiz{ 187 | Content: "张三,李四", 188 | }) 189 | if err != nil { 190 | t.Errorf("Mini.DetectRiskContent returned unexcepted error: %v", err) 191 | } 192 | want := &DetectRiskContentResp{ 193 | Action: "REJECTED", 194 | Keywords: []string{"张三", "李四"}, 195 | UniqueID: "0ba600421493362500440513027526", 196 | } 197 | if !reflect.DeepEqual(got, want) { 198 | t.Errorf("Mini.DetectRiskContent got %+v, want %+v", got, want) 199 | } 200 | } 201 | 202 | func TestMiniService_DetectRiskContent_failed(t *testing.T) { 203 | client, mux, _, tearDown := setup() 204 | defer tearDown() 205 | 206 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 207 | 208 | fmt.Fprint(w, `{ 209 | "alipay_security_risk_content_detect_response": { 210 | "code": "20000", 211 | "msg": "Service Currently Unavailable", 212 | "sub_code": "isp.unknow-error", 213 | "sub_msg": "系统繁忙" 214 | } 215 | }`) 216 | }) 217 | _, err := client.Mini.DetectRiskContent(context.Background(), &DetectRiskContentBiz{ 218 | Content: "张三,李四", 219 | }) 220 | if err == nil { 221 | t.Errorf("Mini.DetectRiskContent excepted error") 222 | } 223 | } 224 | 225 | func TestMiniService_QueryTinyAppExist(t *testing.T) { 226 | client, mux, _, tearDown := setup() 227 | defer tearDown() 228 | 229 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 230 | 231 | fmt.Fprint(w, `{ 232 | "alipay_open_mini_tinyapp_exist_query_response": { 233 | "code": "10000", 234 | "msg": "Success", 235 | "exist_mini": "true" 236 | } 237 | }`) 238 | }) 239 | 240 | got, err := client.Mini.QueryTinyAppExist(context.Background(), &QueryTinyAppExistBiz{ 241 | PID: "2088301371981234", 242 | }) 243 | if err != nil { 244 | t.Errorf("Mini.QueryTinyAppExist returned unexcepted error: %v", err) 245 | } 246 | want := &QueryTinyAppExistResp{ExistMini: "true"} 247 | if !reflect.DeepEqual(got, want) { 248 | t.Errorf("Mini.QueryTinyAppExist got %+v, want %+v", got, want) 249 | } 250 | } 251 | 252 | func TestMiniService_QueryTinyAppExist_failed(t *testing.T) { 253 | client, mux, _, tearDown := setup() 254 | defer tearDown() 255 | 256 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 257 | 258 | fmt.Fprint(w, `{ 259 | "alipay_open_mini_tinyapp_exist_query_response": { 260 | "code": "20000", 261 | "msg": "Service Currently Unavailable", 262 | "sub_code": "isp.unknow-error", 263 | "sub_msg": "系统繁忙" 264 | } 265 | }`) 266 | }) 267 | _, err := client.Mini.QueryTinyAppExist(context.Background(), &QueryTinyAppExistBiz{ 268 | PID: "2088301371981234", 269 | }) 270 | if err == nil { 271 | t.Errorf("Mini.QueryTinyAppExist excepted error") 272 | } 273 | } 274 | 275 | func TestMiniService_QueryCategory(t *testing.T) { 276 | client, mux, _, tearDown := setup() 277 | defer tearDown() 278 | 279 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 280 | 281 | fmt.Fprint(w, `{ 282 | "alipay_open_mini_category_query_response": { 283 | "code": "10000", 284 | "msg": "Success", 285 | "mini_category_list": [ 286 | { 287 | "category_id": "107396", 288 | "category_name": "公共交通", 289 | "parent_category_id": "0", 290 | "has_child": true, 291 | "need_license": true, 292 | "need_out_door_pic": true, 293 | "need_special_license": true 294 | } 295 | ], 296 | "category_list": [ 297 | { 298 | "category_id": "107396", 299 | "category_name": "公共交通", 300 | "parent_category_id": "0", 301 | "has_child": true, 302 | "need_license": true, 303 | "need_out_door_pic": true, 304 | "need_special_license": true 305 | } 306 | ] 307 | } 308 | }`) 309 | }) 310 | 311 | got, err := client.Mini.QueryCategory(context.Background(), &QueryCategoryBiz{ 312 | IsFilter: true, 313 | }) 314 | if err != nil { 315 | t.Errorf("Mini.QueryTinyAppExist returned unexcepted error: %v", err) 316 | } 317 | want := &QueryCategoryResp{ 318 | MiniCategoryList: []*MiniAppCategory{ 319 | {CategoryID: "107396", CategoryName: "公共交通", ParentCategoryID: "0", HasChild: true, NeedLicense: true, NeedOutDoorPic: true, NeedSpecialLicense: true}, 320 | }, 321 | CategoryList: []*MiniAppCategory{ 322 | {CategoryID: "107396", CategoryName: "公共交通", ParentCategoryID: "0", HasChild: true, NeedLicense: true, NeedOutDoorPic: true, NeedSpecialLicense: true}, 323 | }, 324 | } 325 | if !reflect.DeepEqual(got, want) { 326 | t.Errorf("Mini.QueryCategory got %+v, want %+v", got, want) 327 | } 328 | } 329 | 330 | func TestMiniService_QueryCategory_failed(t *testing.T) { 331 | client, mux, _, tearDown := setup() 332 | defer tearDown() 333 | 334 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 335 | 336 | fmt.Fprint(w, `{ 337 | "alipay_open_mini_category_query_response": { 338 | "code": "20000", 339 | "msg": "Service Currently Unavailable", 340 | "sub_code": "isp.unknow-error", 341 | "sub_msg": "系统繁忙" 342 | } 343 | }`) 344 | }) 345 | _, err := client.Mini.QueryCategory(context.Background(), &QueryCategoryBiz{ 346 | IsFilter: true, 347 | }) 348 | if err == nil { 349 | t.Errorf("Mini.QueryCategory excepted error") 350 | } 351 | } 352 | 353 | func TestMiniService_CertifyIndividualBusiness(t *testing.T) { 354 | client, mux, _, tearDown := setup() 355 | defer tearDown() 356 | 357 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 358 | 359 | fmt.Fprint(w, `{ 360 | "alipay_open_mini_individual_business_certify_response": { 361 | "code": "10000", 362 | "msg": "Success", 363 | "certify_result": true 364 | } 365 | }`) 366 | }) 367 | 368 | got, err := client.Mini.CertifyIndividualBusiness(context.Background(), &CertifyIndividualBusinessBiz{ 369 | LiceseNo: "1235234234123124234234", 370 | LicesePic: "/9j/Qnl0ZUFycmF5T3V0cHV0U3RyZWFtIG91dHB1dCA9IG5ldyBCeXRlQ中间缩略Skge30=", 371 | }) 372 | if err != nil { 373 | t.Errorf("Mini.CertifyIndividualBusiness returned unexcepted error: %v", err) 374 | } 375 | want := &CertifyIndividualBusinessResp{ 376 | CertifyResult: true, 377 | } 378 | if !reflect.DeepEqual(got, want) { 379 | t.Errorf("Mini.CertifyIndividualBusiness got %+v, want %+v", got, want) 380 | } 381 | } 382 | 383 | func TestMiniService_CertifyIndividualBusiness_failed(t *testing.T) { 384 | client, mux, _, tearDown := setup() 385 | defer tearDown() 386 | 387 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 388 | 389 | fmt.Fprint(w, `{ 390 | "alipay_open_mini_individual_business_certify_response": { 391 | "code": "20000", 392 | "msg": "Service Currently Unavailable", 393 | "sub_code": "isp.unknow-error", 394 | "sub_msg": "系统繁忙" 395 | } 396 | }`) 397 | }) 398 | _, err := client.Mini.CertifyIndividualBusiness(context.Background(), &CertifyIndividualBusinessBiz{ 399 | LiceseNo: "1235234234123124234234", 400 | LicesePic: "/9j/Qnl0ZUFycmF5T3V0cHV0U3RyZWFtIG91dHB1dCA9IG5ldyBCeXRlQ中间缩略Skge30=", 401 | }) 402 | if err == nil { 403 | t.Errorf("Mini.CertifyIndividualBusiness excepted error") 404 | } 405 | } 406 | 407 | func TestMiniService_SyncContent(t *testing.T) { 408 | client, mux, _, tearDown := setup() 409 | defer tearDown() 410 | 411 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 412 | 413 | fmt.Fprint(w, `{ 414 | "alipay_open_mini_content_sync_response": { 415 | "code": "10000", 416 | "msg": "Success", 417 | "result_data": "true" 418 | } 419 | }`) 420 | }) 421 | 422 | got, err := client.Mini.SyncContent(context.Background(), &SyncContentBiz{ 423 | ContentType: "SHOP", 424 | ContentData: `{"shopIds": ["2020041300077000000024065718"]}`, 425 | Operation: "batchBind", 426 | ExtendInfo: `{"key": "val"}`, 427 | }) 428 | if err != nil { 429 | t.Errorf("Mini.SyncContent returned unexcepted error: %v", err) 430 | } 431 | want := &SyncContentResp{ 432 | ResultData: "true", 433 | } 434 | if !reflect.DeepEqual(got, want) { 435 | t.Errorf("Mini.SyncContent got %+v, want %+v", got, want) 436 | } 437 | } 438 | 439 | func TestMiniService_SyncContent_failed(t *testing.T) { 440 | client, mux, _, tearDown := setup() 441 | defer tearDown() 442 | 443 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 444 | 445 | fmt.Fprint(w, `{ 446 | "alipay_open_mini_content_sync_response": { 447 | "code": "20000", 448 | "msg": "Service Currently Unavailable", 449 | "sub_code": "isp.unknow-error", 450 | "sub_msg": "系统繁忙" 451 | } 452 | }`) 453 | }) 454 | _, err := client.Mini.SyncContent(context.Background(), &SyncContentBiz{ 455 | ContentType: "SHOP", 456 | ContentData: `{"shopIds": ["2020041300077000000024065718"]}`, 457 | Operation: "batchBind", 458 | ExtendInfo: `{"key": "val"}`, 459 | }) 460 | if err == nil { 461 | t.Errorf("Mini.CertifyIndividualBusiness excepted error") 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /alipay/mini_version.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io" 7 | ) 8 | 9 | // QueryVersionListResp 查询小程序列表返回值 10 | type QueryVersionListResp struct { 11 | AppVersions []string `json:"app_versions"` 12 | } 13 | 14 | // QueryVersionList 查询小程序列表 15 | func (s *MiniService) QueryVersionList(ctx context.Context, opts ...ValueOptions) (*QueryVersionListResp, error) { 16 | apiMethod := "alipay.open.mini.version.list.query" 17 | req, err := s.client.NewRequest(apiMethod, nil, opts...) 18 | if err != nil { 19 | return nil, err 20 | } 21 | resp := new(QueryVersionListResp) 22 | _, err = s.client.Do(ctx, req, resp) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return resp, nil 27 | } 28 | 29 | // DeleteVersionBiz 小程序删除版本 30 | type DeleteVersionBiz struct { 31 | AppVersion string `json:"app_version"` //小程序版本号 32 | BundleID string `json:"bundle_id"` //小程序投放的端参数,例如投放到支付宝钱包是支付宝端。该参数可选,默认支付宝端,目前仅支持支付宝端,枚举列举:com.alipay.alipaywallet:支付宝端 33 | } 34 | 35 | // DeleteVersion 小程序删除版本 36 | func (s *MiniService) DeleteVersion(ctx context.Context, biz *DeleteVersionBiz, opts ...ValueOptions) error { 37 | apiMethod := "alipay.open.mini.version.delete" 38 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 39 | if err != nil { 40 | return err 41 | } 42 | _, err = s.client.Do(ctx, req, nil) 43 | if err != nil { 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | // ApplyVersionAuditBiz 小程序提交审核 50 | type ApplyVersionAuditBiz struct { 51 | LicenseName string `json:"license_name,omitempty"` 52 | FirstLicensePic *File `json:"first_license_pic,omitempty"` 53 | SecondLicensePic *File `json:"second_license_pic,omitempty"` 54 | ThirdLicensePic *File `json:"third_license_pic,omitempty"` 55 | FourthLicensePic *File `json:"fourth_license_pic,omitempty"` 56 | FifthLicensePic *File `json:"fifth_license_pic,omitempty"` 57 | LicenseValidDate string `json:"license_valid_date,omitempty"` 58 | OutDoorPic *File `json:"out_door_pic,omitempty"` 59 | AppVersion string `json:"app_version"` 60 | AppName string `json:"app_name,omitempty"` 61 | AppEnglishName string `json:"app_english_name,omitempty"` 62 | AppSlogan string `json:"app_slogan,omitempty"` 63 | AppLogo *File `json:"app_logo,omitempty"` 64 | AppCategoryIDs string `json:"app_category_ids,omitempty"` 65 | AppDesc string `json:"app_desc,omitempty"` 66 | ServicePhone string `json:"service_phone,omitempty"` 67 | ServiceEmail string `json:"service_email,omitempty"` 68 | VersionDesc string `json:"version_desc"` 69 | Memo string `json:"memo,omitempty"` 70 | RegionType string `json:"region_type"` 71 | ServiceRegionInfo []*RegionInfo `json:"service_region_info,omitempty"` 72 | FirstScreenShot *File `json:"first_screen_shot,omitempty"` 73 | SecondScreenShot *File `json:"second_screen_shot,omitempty"` 74 | ThirdScreenShot *File `json:"third_screen_shot,omitempty"` 75 | FourthScreenShot *File `json:"fourth_screen_shot,omitempty"` 76 | FifthScreenShot *File `json:"fifth_screen_shot,omitempty"` 77 | LicenseNo string `json:"license_no,omitempty"` 78 | MiniCategoryIDs string `json:"mini_category_ids,omitempty"` 79 | FirstSpecialLicensePic *File `json:"first_special_license_pic,omitempty"` 80 | SecondSpecialLicensePic *File `json:"second_special_license_pic,omitempty"` 81 | ThirdSpecialLicensePic *File `json:"third_special_license_pic,omitempty"` 82 | TestAccount string `json:"test_accout,omitempty"` // 官方拼写错误 83 | TestPassword string `json:"test_password,omitempty"` 84 | TestFileName *File `json:"test_file_name,omitempty"` 85 | BundleID string `json:"bundle_id,omitempty"` 86 | } 87 | 88 | func (a ApplyVersionAuditBiz) Params() map[string]string { 89 | params := make(map[string]string) 90 | if a.LicenseName != "" { 91 | params["license_name"] = a.LicenseName 92 | } 93 | if a.LicenseValidDate != "" { 94 | params["license_valid_date"] = a.LicenseValidDate 95 | } 96 | if a.AppVersion != "" { 97 | params["app_version"] = a.AppVersion 98 | } 99 | if a.AppName != "" { 100 | params["app_name"] = a.AppName 101 | } 102 | if a.AppEnglishName != "" { 103 | params["app_english_name"] = a.AppEnglishName 104 | } 105 | if a.AppSlogan != "" { 106 | params["app_slogan"] = a.AppSlogan 107 | } 108 | if a.AppCategoryIDs != "" { 109 | params["app_category_ids"] = a.AppCategoryIDs 110 | } 111 | if a.AppDesc != "" { 112 | params["app_desc"] = a.AppDesc 113 | } 114 | if a.ServicePhone != "" { 115 | params["service_phone"] = a.ServicePhone 116 | } 117 | if a.ServiceEmail != "" { 118 | params["service_email"] = a.ServiceEmail 119 | } 120 | if a.VersionDesc != "" { 121 | params["version_desc"] = a.VersionDesc 122 | } 123 | if a.Memo != "" { 124 | params["memo"] = a.Memo 125 | } 126 | if a.RegionType != "" { 127 | params["region_type"] = a.RegionType 128 | } 129 | if a.ServiceRegionInfo != nil { 130 | serviceRegionInfo, _ := json.Marshal(a.ServiceRegionInfo) 131 | params["service_region_info"] = string(serviceRegionInfo) 132 | } 133 | if a.LicenseNo != "" { 134 | params["license_no"] = a.LicenseNo 135 | } 136 | if a.MiniCategoryIDs != "" { 137 | params["mini_category_ids"] = a.MiniCategoryIDs 138 | } 139 | if a.TestAccount != "" { 140 | params["test_accout"] = a.TestAccount 141 | } 142 | if a.TestPassword != "" { 143 | params["test_password"] = a.TestPassword 144 | } 145 | if a.BundleID != "" { 146 | params["bundle_id"] = a.BundleID 147 | } 148 | return params 149 | } 150 | 151 | func (a ApplyVersionAuditBiz) MultipartParams() map[string]io.Reader { 152 | params := make(map[string]io.Reader) 153 | if a.FirstLicensePic != nil { 154 | params["first_license_pic"] = a.FirstLicensePic 155 | } 156 | if a.SecondLicensePic != nil { 157 | params["second_license_pic"] = a.SecondLicensePic 158 | } 159 | if a.ThirdLicensePic != nil { 160 | params["third_license_pic"] = a.ThirdLicensePic 161 | } 162 | if a.FourthLicensePic != nil { 163 | params["fourth_license_pic"] = a.FourthLicensePic 164 | } 165 | if a.FifthLicensePic != nil { 166 | params["fifth_license_pic"] = a.FifthLicensePic 167 | } 168 | if a.OutDoorPic != nil { 169 | params["out_door_pic"] = a.OutDoorPic 170 | } 171 | if a.AppLogo != nil { 172 | params["app_logo"] = a.AppLogo 173 | } 174 | if a.FirstScreenShot != nil { 175 | params["first_screen_shot"] = a.FirstScreenShot 176 | } 177 | if a.SecondScreenShot != nil { 178 | params["second_screen_shot"] = a.SecondScreenShot 179 | } 180 | if a.ThirdScreenShot != nil { 181 | params["third_screen_shot"] = a.ThirdScreenShot 182 | } 183 | if a.FourthScreenShot != nil { 184 | params["fourth_screen_shot"] = a.FourthScreenShot 185 | } 186 | if a.FifthScreenShot != nil { 187 | params["fifth_screen_shot"] = a.FifthScreenShot 188 | } 189 | if a.FirstSpecialLicensePic != nil { 190 | params["first_special_license_pic"] = a.FirstSpecialLicensePic 191 | } 192 | if a.SecondSpecialLicensePic != nil { 193 | params["second_special_license_pic"] = a.SecondSpecialLicensePic 194 | } 195 | if a.ThirdSpecialLicensePic != nil { 196 | params["third_special_license_pic"] = a.ThirdSpecialLicensePic 197 | } 198 | if a.TestFileName != nil { 199 | params["test_file_name"] = a.TestFileName 200 | } 201 | return params 202 | } 203 | 204 | // RegionInfo 省市区信息,当区域类型为LOCATION时,不能为空 205 | type RegionInfo struct { 206 | ProvinceCode string `json:"province_code"` 207 | ProvinceName string `json:"province_name"` 208 | CityCode string `json:"city_code"` 209 | CityName string `json:"city_name"` 210 | AreaCode string `json:"area_code"` 211 | AreaName string `json:"area_name"` 212 | } 213 | 214 | // ApplyVersionAudit 小程序提交审核 215 | func (s *MiniService) ApplyVersionAudit(ctx context.Context, biz *ApplyVersionAuditBiz, opts ...ValueOptions) error { 216 | apiMethod := "alipay.open.mini.version.audit.apply" 217 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 218 | if err != nil { 219 | return err 220 | } 221 | _, err = s.client.Do(ctx, req, nil) 222 | if err != nil { 223 | return err 224 | } 225 | return nil 226 | } 227 | 228 | // CancelVersionAuditBiz 小程序撤销审核 229 | type CancelVersionAuditBiz struct { 230 | AppVersion string `json:"app_version"` //小程序版本号, 可不选, 默认撤消正在审核中的版本 231 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端(com.alipay.alipaywallet:支付宝端) 232 | } 233 | 234 | // CancelVersionAudit 小程序撤销审核 235 | func (s *MiniService) CancelVersionAudit(ctx context.Context, biz *CancelVersionAuditBiz, opts ...ValueOptions) error { 236 | apiMethod := "alipay.open.mini.version.audit.cancel" 237 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 238 | if err != nil { 239 | return err 240 | } 241 | _, err = s.client.Do(ctx, req, nil) 242 | if err != nil { 243 | return err 244 | } 245 | return nil 246 | } 247 | 248 | // CancelVersionAuditedBiz 小程序退回开发 249 | type CancelVersionAuditedBiz struct { 250 | AppVersion string `json:"app_version"` //小程序版本号 251 | BundleID string `json:"bundle_id"` //小程序投放的端参数,例如投放到支付宝钱包是支付宝端。该参数可选,默认支付宝端,目前仅支持支付宝端,枚举列举:com.alipay.alipaywallet:支付宝端 252 | } 253 | 254 | // CancelVersionAudited 小程序退回开发 255 | func (s *MiniService) CancelVersionAudited(ctx context.Context, biz *CancelVersionAuditedBiz, opts ...ValueOptions) error { 256 | apiMethod := "alipay.open.mini.version.audited.cancel" 257 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 258 | if err != nil { 259 | return err 260 | } 261 | _, err = s.client.Do(ctx, req, nil) 262 | if err != nil { 263 | return err 264 | } 265 | return nil 266 | } 267 | 268 | // OnlineVersionBiz 小程序上架 269 | type OnlineVersionBiz struct { 270 | AppVersion string `json:"app_version"` //小程序版本号, 必选 271 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 272 | } 273 | 274 | // OnlineVersion 小程序上架 275 | func (s *MiniService) OnlineVersion(ctx context.Context, biz *OnlineVersionBiz, opts ...ValueOptions) error { 276 | apiMethod := "alipay.open.mini.version.online" 277 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 278 | if err != nil { 279 | return err 280 | } 281 | _, err = s.client.Do(ctx, req, nil) 282 | if err != nil { 283 | return err 284 | } 285 | return nil 286 | } 287 | 288 | // OfflineVersionBiz 小程序下架 289 | type OfflineVersionBiz struct { 290 | AppVersion string `json:"app_version"` //小程序版本号, 必选 291 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 292 | } 293 | 294 | // OfflineVersion 小程序下架 295 | func (s *MiniService) OfflineVersion(ctx context.Context, biz *OfflineVersionBiz, opts ...ValueOptions) error { 296 | apiMethod := "alipay.open.mini.version.offline" 297 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 298 | if err != nil { 299 | return err 300 | } 301 | _, err = s.client.Do(ctx, req, nil) 302 | if err != nil { 303 | return err 304 | } 305 | return nil 306 | } 307 | 308 | // RollbackVersionBiz 小程序回滚 309 | type RollbackVersionBiz struct { 310 | AppVersion string `json:"app_version"` //小程序版本号, 必选 311 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 312 | } 313 | 314 | // RollbackVersion 小程序回滚 315 | func (s *MiniService) RollbackVersion(ctx context.Context, biz *RollbackVersionBiz, opts ...ValueOptions) error { 316 | apiMethod := "alipay.open.mini.version.rollback" 317 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 318 | if err != nil { 319 | return err 320 | } 321 | _, err = s.client.Do(ctx, req, nil) 322 | if err != nil { 323 | return err 324 | } 325 | return nil 326 | } 327 | 328 | // OnlineGrayVersionBiz 小程序灰度上架 329 | type OnlineGrayVersionBiz struct { 330 | AppVersion string `json:"app_version"` //小程序版本号, 必选 331 | GrayStrategy string `json:"gray_strategy"` //小程序灰度策略值,支持p10,p30,p50, 代表百分之多少的用户 332 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 333 | } 334 | 335 | // OnlineGrayVersion 小程序灰度上架 336 | func (s *MiniService) OnlineGrayVersion(ctx context.Context, biz *OnlineGrayVersionBiz, opts ...ValueOptions) error { 337 | apiMethod := "alipay.open.mini.version.gray.online" 338 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 339 | if err != nil { 340 | return err 341 | } 342 | _, err = s.client.Do(ctx, req, nil) 343 | if err != nil { 344 | return err 345 | } 346 | return nil 347 | } 348 | 349 | // CancelGrayVersionBiz 小程序结束灰度 350 | type CancelGrayVersionBiz struct { 351 | AppVersion string `json:"app_version"` //小程序版本号, 必选 352 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 353 | } 354 | 355 | // CancelGrayVersion 小程序灰度上架 356 | func (s *MiniService) CancelGrayVersion(ctx context.Context, biz *CancelGrayVersionBiz, opts ...ValueOptions) error { 357 | apiMethod := "alipay.open.mini.version.gray.cancel" 358 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 359 | if err != nil { 360 | return err 361 | } 362 | _, err = s.client.Do(ctx, req, nil) 363 | if err != nil { 364 | return err 365 | } 366 | return nil 367 | } 368 | 369 | // UploadVersionBiz 小程序基于模板上传版本 370 | type UploadVersionBiz struct { 371 | AppVersion string `json:"app_version"` //小程序版本号, 必选 372 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 373 | TemplateID string `json:"template_id"` //模板id 374 | Ext string `json:"ext"` //模板的配置参数 375 | TemplateVersion string `json:"template_version"` //模板版本号,版本号必须满足 x.y.z, 且均为数字。不传默认使用最新在架模板版本。 376 | } 377 | 378 | // UploadVersion 小程序基于模板上传版本 379 | func (s *MiniService) UploadVersion(ctx context.Context, biz *UploadVersionBiz, opts ...ValueOptions) error { 380 | apiMethod := "alipay.open.mini.version.upload" 381 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 382 | if err != nil { 383 | return err 384 | } 385 | _, err = s.client.Do(ctx, req, nil) 386 | if err != nil { 387 | return err 388 | } 389 | return nil 390 | } 391 | 392 | // QueryVersionDetailBiz 小程序版本详情查询 393 | type QueryVersionDetailBiz struct { 394 | AppVersion string `json:"app_version"` //小程序版本号, 必选 395 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 396 | } 397 | 398 | // MiniAppCategoryInfo 小程序类目 399 | type MiniAppCategoryInfo struct { 400 | FirstCategoryID string `json:"first_category_id"` 401 | FirstCategoryName string `json:"first_category_name"` 402 | SecondCategoryID string `json:"second_category_id"` 403 | SecondCategoryName string `json:"second_category_name"` 404 | } 405 | 406 | // MiniPackageInfo 小程序功能包 407 | type MiniPackageInfo struct { 408 | PackageName string `json:"package_name"` 409 | PackageDesc string `json:"package_desc"` 410 | DocURL string `json:"doc_url"` 411 | Status string `json:"status"` 412 | PackageOpenType string `json:"package_open_type"` 413 | } 414 | 415 | // VersionDetail 小程序版本详情 416 | type VersionDetail struct { 417 | AppVersion string `json:"app_version"` 418 | AppName string `json:"app_name"` 419 | AppEnglishName string `json:"app_english_name"` 420 | AppLogo string `json:"app_logo"` 421 | VersionDesc string `json:"version_desc"` 422 | GrayStrategy string `json:"gray_strategy"` 423 | Status string `json:"status"` 424 | RejectReason string `json:"reject_reason"` 425 | ScanResult string `json:"scan_result"` 426 | GmtCreate string `json:"gmt_create"` 427 | GmtApplyAudit string `json:"gmt_apply_audit"` 428 | GmtOnline string `json:"gmt_online"` 429 | GmtOffline string `json:"gmt_offline"` 430 | GmtAuditEnd string `json:"gmt_audit_end"` 431 | AppDesc string `json:"app_desc"` 432 | ServiceRegionType string `json:"service_region_type"` 433 | ServiceRegionInfo []*RegionInfo `json:"service_region_info"` 434 | ScreenShotList []string `json:"screen_shot_list"` 435 | AppSlogan string `json:"app_slogan"` 436 | Memo string `json:"memo"` 437 | ServicePhone string `json:"service_phone"` 438 | ServiceEmail string `json:"service_email"` 439 | MiniAppCategoryInfoList []*MiniAppCategoryInfo `json:"mini_app_category_info_list"` 440 | PackageInfoList []*MiniPackageInfo `json:"package_info_list"` 441 | } 442 | 443 | // QueryVersionDetail 小程序版本详情查询 444 | func (s *MiniService) QueryVersionDetail(ctx context.Context, biz *QueryVersionDetailBiz, opts ...ValueOptions) (*VersionDetail, error) { 445 | apiMethod := "alipay.open.mini.version.detail.query" 446 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 447 | if err != nil { 448 | return nil, err 449 | } 450 | versionDetail := new(VersionDetail) 451 | _, err = s.client.Do(ctx, req, versionDetail) 452 | if err != nil { 453 | return nil, err 454 | } 455 | return versionDetail, nil 456 | } 457 | 458 | // QueryVersionBuildBiz 小程序查询版本构建状态 459 | type QueryVersionBuildBiz struct { 460 | AppVersion string `json:"app_version"` //小程序版本号, 必选 461 | BundleID string `json:"bundle_id"` //端参数,可不选,默认支付宝端 462 | } 463 | 464 | // QueryVersionBuildResp 小程序查询版本构建状态resp 465 | type QueryVersionBuildResp struct { 466 | NeedRotation string `json:"need_rotation"` // 是否需要轮询 467 | CreateStatus string `json:"create_status"` // 创建版本的状态,0-构建排队中;1-正在构建;2-构建成功;3-构建失败;5-构建超时;6-版本创建成功 468 | } 469 | 470 | // QueryVersionBuild 小程序查询版本构建状态 471 | func (s *MiniService) QueryVersionBuild(ctx context.Context, biz *QueryVersionBuildBiz, opts ...ValueOptions) (*QueryVersionBuildResp, error) { 472 | apiMethod := "alipay.open.mini.version.detail.query" 473 | req, err := s.client.NewRequest(apiMethod, biz, opts...) 474 | if err != nil { 475 | return nil, err 476 | } 477 | resp := new(QueryVersionBuildResp) 478 | _, err = s.client.Do(ctx, req, resp) 479 | if err != nil { 480 | return nil, err 481 | } 482 | return resp, nil 483 | } 484 | -------------------------------------------------------------------------------- /alipay/mini_version_test.go: -------------------------------------------------------------------------------- 1 | package alipay 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestMiniService_ApplyVersionAudit(t *testing.T) { 13 | client, mux, _, tearDown := setup() 14 | defer tearDown() 15 | 16 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 17 | 18 | fmt.Fprint(w, `{ 19 | "alipay_open_mini_version_audit_apply_response": { 20 | "code": "10000", 21 | "msg": "Success" 22 | } 23 | }`) 24 | }) 25 | 26 | err := client.Mini.ApplyVersionAudit(context.Background(), &ApplyVersionAuditBiz{ 27 | LicenseName: "营业执照名称", 28 | FirstLicensePic: &File{ 29 | Name: "FirstLicensePic", 30 | Content: bytes.NewReader([]byte("FirstLicensePic")), 31 | }, 32 | SecondLicensePic: &File{ 33 | Name: "SecondLicensePic", 34 | Content: bytes.NewReader([]byte("SecondLicensePic")), 35 | }, 36 | ThirdLicensePic: &File{ 37 | Name: "ThirdLicensePic", 38 | Content: bytes.NewReader([]byte("ThirdLicensePic")), 39 | }, 40 | FourthLicensePic: &File{ 41 | Name: "FourthLicensePic", 42 | Content: bytes.NewReader([]byte("FourthLicensePic")), 43 | }, 44 | FifthLicensePic: &File{ 45 | Name: "FifthLicensePic", 46 | Content: bytes.NewReader([]byte("FifthLicensePic")), 47 | }, 48 | LicenseValidDate: "9999-12-31", 49 | OutDoorPic: &File{ 50 | Name: "OutDoorPic", 51 | Content: bytes.NewReader([]byte("OutDoorPic")), 52 | }, 53 | AppVersion: "0.0.1", 54 | AppName: "小程序示例", 55 | AppEnglishName: "demo example", 56 | AppSlogan: "这是一个支付示例", 57 | AppLogo: &File{ 58 | Name: "AppLogo", 59 | Content: bytes.NewReader([]byte("AppLogo")), 60 | }, 61 | AppCategoryIDs: "11_12;12_13", 62 | AppDesc: "这是一个小程序的描述这是一个小程序的描述这是一个小程序的描述这是一个小程序的描述", 63 | ServicePhone: "13110101010", 64 | ServiceEmail: "example@mail.com", 65 | VersionDesc: "小程序版本描述小程序版本描述小程序版本描述小程序版本描述小程序版本描述小程序版本描述", 66 | Memo: "小程序示例", 67 | RegionType: "LOCATION", 68 | ServiceRegionInfo: []*RegionInfo{ 69 | { 70 | ProvinceCode: "310000", 71 | ProvinceName: "浙江省", 72 | CityCode: "310000", 73 | CityName: "杭州市", 74 | AreaCode: "311100", 75 | AreaName: "余杭区", 76 | }, 77 | }, 78 | FirstScreenShot: &File{ 79 | Name: "FirstScreenShot", 80 | Content: bytes.NewReader([]byte("FirstScreenShot")), 81 | }, 82 | SecondScreenShot: &File{ 83 | Name: "SecondScreenShot", 84 | Content: bytes.NewReader([]byte("SecondScreenShot")), 85 | }, 86 | ThirdScreenShot: &File{ 87 | Name: "ThirdScreenShot", 88 | Content: bytes.NewReader([]byte("ThirdScreenShot")), 89 | }, 90 | FourthScreenShot: &File{ 91 | Name: "FourthScreenShot", 92 | Content: bytes.NewReader([]byte("FourthScreenShot")), 93 | }, 94 | FifthScreenShot: &File{ 95 | Name: "FifthScreenShot", 96 | Content: bytes.NewReader([]byte("FifthScreenShot")), 97 | }, 98 | LicenseNo: "LicenseNo", 99 | MiniCategoryIDs: "MiniCategoryIDs", 100 | FirstSpecialLicensePic: &File{ 101 | Name: "FirstSpecialLicensePic", 102 | Content: bytes.NewReader([]byte("FirstSpecialLicensePic")), 103 | }, 104 | SecondSpecialLicensePic: &File{ 105 | Name: "SecondSpecialLicensePic", 106 | Content: bytes.NewReader([]byte("SecondSpecialLicensePic")), 107 | }, 108 | ThirdSpecialLicensePic: &File{ 109 | Name: "ThirdSpecialLicensePic", 110 | Content: bytes.NewReader([]byte("ThirdSpecialLicensePic")), 111 | }, 112 | TestAccount: "TestAccount", 113 | TestPassword: "TestPassword", 114 | TestFileName: &File{ 115 | Name: "TestFileName", 116 | Content: bytes.NewReader([]byte("TestFileName")), 117 | }, 118 | BundleID: "com.alipay.alipaywallet", 119 | }) 120 | 121 | if err != nil { 122 | t.Errorf("Mini.CancelExperience returned unexcepted error: %v", err) 123 | } 124 | } 125 | 126 | func TestMiniService_ApplyVersionAudit_failed(t *testing.T) { 127 | client, mux, _, tearDown := setup() 128 | defer tearDown() 129 | 130 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 131 | 132 | fmt.Fprint(w, `{ 133 | "alipay_open_app_members_query_response": { 134 | "code": "20000", 135 | "msg": "Service Currently Unavailable", 136 | "sub_code": "isp.unknow-error", 137 | "sub_msg": "系统繁忙" 138 | } 139 | }`) 140 | }) 141 | err := client.Mini.ApplyVersionAudit(context.Background(), &ApplyVersionAuditBiz{}) 142 | if err == nil { 143 | t.Errorf("Mini.ApplyVersionAudit excepted error") 144 | } 145 | } 146 | 147 | func TestMiniService_CancelGrayVersion(t *testing.T) { 148 | client, mux, _, tearDown := setup() 149 | defer tearDown() 150 | 151 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 152 | 153 | fmt.Fprint(w, `{ 154 | "alipay_open_mini_version_gray_cancel_response": { 155 | "code": "10000", 156 | "msg": "Success" 157 | } 158 | }`) 159 | }) 160 | 161 | err := client.Mini.CancelGrayVersion(context.Background(), &CancelGrayVersionBiz{ 162 | AppVersion: "0.0.1", 163 | BundleID: "com.alipay.alipaywallet", 164 | }) 165 | if err != nil { 166 | t.Errorf("Mini.CancelGrayVersion returned unexcepted error: %v", err) 167 | } 168 | } 169 | 170 | func TestMiniService_CancelGrayVersion_failed(t *testing.T) { 171 | client, mux, _, tearDown := setup() 172 | defer tearDown() 173 | 174 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 175 | 176 | fmt.Fprint(w, `{ 177 | "alipay_open_mini_version_gray_version_response": { 178 | "code": "20000", 179 | "msg": "Service Currently Unavailable", 180 | "sub_code": "isp.unknow-error", 181 | "sub_msg": "系统繁忙" 182 | } 183 | }`) 184 | }) 185 | err := client.Mini.CancelGrayVersion(context.Background(), &CancelGrayVersionBiz{ 186 | AppVersion: "0.0.1", 187 | BundleID: "com.alipay.alipaywallet", 188 | }) 189 | if err == nil { 190 | t.Errorf("Mini.CancelGrayVersion excepted error") 191 | } 192 | } 193 | 194 | func TestMiniService_CancelVersionAudit(t *testing.T) { 195 | client, mux, _, tearDown := setup() 196 | defer tearDown() 197 | 198 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 199 | 200 | fmt.Fprint(w, `{ 201 | "alipay_open_mini_version_audit_cancel_response": { 202 | "code": "10000", 203 | "msg": "Success" 204 | } 205 | }`) 206 | }) 207 | 208 | err := client.Mini.CancelVersionAudit(context.Background(), &CancelVersionAuditBiz{ 209 | AppVersion: "0.0.1", 210 | BundleID: "com.alipay.alipaywallet", 211 | }) 212 | if err != nil { 213 | t.Errorf("Mini.CancelVersionAudit returned unexcepted error: %v", err) 214 | } 215 | } 216 | 217 | func TestMiniService_CancelVersionAudit_failed(t *testing.T) { 218 | client, mux, _, tearDown := setup() 219 | defer tearDown() 220 | 221 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 222 | 223 | fmt.Fprint(w, `{ 224 | "alipay_open_mini_version_audit_cancel_response": { 225 | "code": "20000", 226 | "msg": "Service Currently Unavailable", 227 | "sub_code": "isp.unknow-error", 228 | "sub_msg": "系统繁忙" 229 | } 230 | }`) 231 | }) 232 | err := client.Mini.CancelVersionAudit(context.Background(), &CancelVersionAuditBiz{ 233 | AppVersion: "0.0.1", 234 | BundleID: "com.alipay.alipaywallet", 235 | }) 236 | if err == nil { 237 | t.Errorf("Mini.CancelVersionAudit excepted error") 238 | } 239 | } 240 | 241 | func TestMiniService_CancelVersionAudited(t *testing.T) { 242 | client, mux, _, tearDown := setup() 243 | defer tearDown() 244 | 245 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 246 | 247 | fmt.Fprint(w, `{ 248 | "alipay_open_mini_version_audited_cancel_response": { 249 | "code": "10000", 250 | "msg": "Success" 251 | } 252 | }`) 253 | }) 254 | 255 | err := client.Mini.CancelVersionAudited(context.Background(), &CancelVersionAuditedBiz{ 256 | AppVersion: "0.0.1", 257 | BundleID: "com.alipay.alipaywallet", 258 | }) 259 | if err != nil { 260 | t.Errorf("Mini.CancelVersionAudited returned unexcepted error: %v", err) 261 | } 262 | } 263 | 264 | func TestMiniService_CancelVersionAudited_failed(t *testing.T) { 265 | client, mux, _, tearDown := setup() 266 | defer tearDown() 267 | 268 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 269 | 270 | fmt.Fprint(w, `{ 271 | "alipay_open_mini_version_audited_cancel_response": { 272 | "code": "20000", 273 | "msg": "Service Currently Unavailable", 274 | "sub_code": "isp.unknow-error", 275 | "sub_msg": "系统繁忙" 276 | } 277 | }`) 278 | }) 279 | err := client.Mini.CancelVersionAudited(context.Background(), &CancelVersionAuditedBiz{ 280 | AppVersion: "0.0.1", 281 | BundleID: "com.alipay.alipaywallet", 282 | }) 283 | if err == nil { 284 | t.Errorf("Mini.CancelVersionAudited excepted error") 285 | } 286 | } 287 | 288 | func TestMiniService_DeleteVersion(t *testing.T) { 289 | client, mux, _, tearDown := setup() 290 | defer tearDown() 291 | 292 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 293 | 294 | fmt.Fprint(w, `{ 295 | "alipay_open_mini_version_delete_response": { 296 | "code": "10000", 297 | "msg": "Success" 298 | } 299 | }`) 300 | }) 301 | 302 | err := client.Mini.DeleteVersion(context.Background(), &DeleteVersionBiz{ 303 | AppVersion: "0.0.1", 304 | BundleID: "com.alipay.alipaywallet", 305 | }) 306 | if err != nil { 307 | t.Errorf("Mini.DeleteVersion returned unexcepted error: %v", err) 308 | } 309 | } 310 | 311 | func TestMiniService_DeleteVersion_failed(t *testing.T) { 312 | client, mux, _, tearDown := setup() 313 | defer tearDown() 314 | 315 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 316 | 317 | fmt.Fprint(w, `{ 318 | "alipay_open_mini_version_delete_response": { 319 | "code": "20000", 320 | "msg": "Service Currently Unavailable", 321 | "sub_code": "isp.unknow-error", 322 | "sub_msg": "系统繁忙" 323 | } 324 | }`) 325 | }) 326 | err := client.Mini.DeleteVersion(context.Background(), &DeleteVersionBiz{ 327 | AppVersion: "0.0.1", 328 | BundleID: "com.alipay.alipaywallet", 329 | }) 330 | if err == nil { 331 | t.Errorf("Mini.DeleteVersion excepted error") 332 | } 333 | } 334 | 335 | func TestMiniService_OfflineVersion(t *testing.T) { 336 | client, mux, _, tearDown := setup() 337 | defer tearDown() 338 | 339 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 340 | 341 | fmt.Fprint(w, `{ 342 | "alipay_open_mini_version_offline_response": { 343 | "code": "10000", 344 | "msg": "Success" 345 | } 346 | }`) 347 | }) 348 | 349 | err := client.Mini.OfflineVersion(context.Background(), &OfflineVersionBiz{ 350 | AppVersion: "0.0.1", 351 | BundleID: "com.alipay.alipaywallet", 352 | }) 353 | if err != nil { 354 | t.Errorf("Mini.OfflineVersion returned unexcepted error: %v", err) 355 | } 356 | } 357 | 358 | func TestMiniService_OfflineVersion_failed(t *testing.T) { 359 | client, mux, _, tearDown := setup() 360 | defer tearDown() 361 | 362 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 363 | 364 | fmt.Fprint(w, `{ 365 | "alipay_open_mini_version_offline_response": { 366 | "code": "20000", 367 | "msg": "Service Currently Unavailable", 368 | "sub_code": "isp.unknow-error", 369 | "sub_msg": "系统繁忙" 370 | } 371 | }`) 372 | }) 373 | err := client.Mini.OfflineVersion(context.Background(), &OfflineVersionBiz{ 374 | AppVersion: "0.0.1", 375 | BundleID: "com.alipay.alipaywallet", 376 | }) 377 | if err == nil { 378 | t.Errorf("Mini.OfflineVersion excepted error") 379 | } 380 | } 381 | 382 | func TestMiniService_OnlineGrayVersion(t *testing.T) { 383 | client, mux, _, tearDown := setup() 384 | defer tearDown() 385 | 386 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 387 | 388 | fmt.Fprint(w, `{ 389 | "alipay_open_mini_version_gray_offline_response": { 390 | "code": "10000", 391 | "msg": "Success" 392 | } 393 | }`) 394 | }) 395 | 396 | err := client.Mini.OnlineGrayVersion(context.Background(), &OnlineGrayVersionBiz{ 397 | AppVersion: "0.0.1", 398 | BundleID: "com.alipay.alipaywallet", 399 | }) 400 | if err != nil { 401 | t.Errorf("Mini.OfflineVersion returned unexcepted error: %v", err) 402 | } 403 | } 404 | 405 | func TestMiniService_OnlineGrayVersion_failed(t *testing.T) { 406 | client, mux, _, tearDown := setup() 407 | defer tearDown() 408 | 409 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 410 | 411 | fmt.Fprint(w, `{ 412 | "alipay_open_mini_version_gray_offline_response": { 413 | "code": "20000", 414 | "msg": "Service Currently Unavailable", 415 | "sub_code": "isp.unknow-error", 416 | "sub_msg": "系统繁忙" 417 | } 418 | }`) 419 | }) 420 | err := client.Mini.OnlineGrayVersion(context.Background(), &OnlineGrayVersionBiz{ 421 | AppVersion: "0.0.1", 422 | BundleID: "com.alipay.alipaywallet", 423 | }) 424 | if err == nil { 425 | t.Errorf("Mini.OnlineGrayVersion excepted error") 426 | } 427 | } 428 | 429 | func TestMiniService_OnlineVersion(t *testing.T) { 430 | client, mux, _, tearDown := setup() 431 | defer tearDown() 432 | 433 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 434 | 435 | fmt.Fprint(w, `{ 436 | "alipay_open_mini_version_online_response": { 437 | "code": "10000", 438 | "msg": "Success" 439 | } 440 | }`) 441 | }) 442 | 443 | err := client.Mini.OnlineVersion(context.Background(), &OnlineVersionBiz{ 444 | AppVersion: "0.0.1", 445 | BundleID: "com.alipay.alipaywallet", 446 | }) 447 | if err != nil { 448 | t.Errorf("Mini.OnlineVersion returned unexcepted error: %v", err) 449 | } 450 | } 451 | 452 | func TestMiniService_OnlineVersion_failed(t *testing.T) { 453 | client, mux, _, tearDown := setup() 454 | defer tearDown() 455 | 456 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 457 | 458 | fmt.Fprint(w, `{ 459 | "alipay_open_mini_version_online_response": { 460 | "code": "20000", 461 | "msg": "Service Currently Unavailable", 462 | "sub_code": "isp.unknow-error", 463 | "sub_msg": "系统繁忙" 464 | } 465 | }`) 466 | }) 467 | err := client.Mini.OnlineVersion(context.Background(), &OnlineVersionBiz{ 468 | AppVersion: "0.0.1", 469 | BundleID: "com.alipay.alipaywallet", 470 | }) 471 | if err == nil { 472 | t.Errorf("Mini.OnlineVersion excepted error") 473 | } 474 | } 475 | 476 | func TestMiniService_QueryVersionDetail(t *testing.T) { 477 | client, mux, _, tearDown := setup() 478 | defer tearDown() 479 | 480 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 481 | 482 | fmt.Fprint(w, `{ 483 | "alipay_open_mini_version_detail_query_response": { 484 | "code": "10000", 485 | "msg": "Success", 486 | "app_version": "0.0.1", 487 | "app_name": "小程序demo", 488 | "app_english_name": "demo example", 489 | "app_logo": "http://image.aaaa.alipay.com", 490 | "version_desc": "这是一个简单的版本描述", 491 | "gray_strategy": "p10", 492 | "status": "INIT", 493 | "reject_reason": "名称太宽泛", 494 | "scan_result": "True", 495 | "gmt_create": "2017-12-12 12:00:00", 496 | "gmt_apply_audit": "2017-12-12 12:00:00", 497 | "gmt_online": "2017-12-12 12:00:00", 498 | "gmt_offline": "2017-12-12 12:00:00", 499 | "app_desc": "小程序demo的相关示例", 500 | "gmt_audit_end": "2017-12-12 12:00:00", 501 | "service_region_type": "LOCATION", 502 | "service_region_info": [ 503 | { 504 | "province_code": "310000", 505 | "province_name": "浙江省", 506 | "city_code": "310000", 507 | "city_name": "杭州市", 508 | "area_code": "311100", 509 | "area_name": "余杭区" 510 | } 511 | ], 512 | "screen_shot_list": [ 513 | "http://image.aaa.alipay.com" 514 | ], 515 | "app_slogan": "小程序demo简介", 516 | "memo": "这是一个demo示例", 517 | "service_phone": "13110101010", 518 | "service_email": "example@mail.com", 519 | "mini_app_category_info_list": [ 520 | { 521 | "first_category_id": "1234", 522 | "first_category_name": "生活服务", 523 | "second_category_id": "12344", 524 | "second_category_name": "汽车服务" 525 | } 526 | ], 527 | "package_info_list": [ 528 | { 529 | "package_name": "基础能力", 530 | "package_desc": "这是通用能力", 531 | "doc_url": "http://doc.aaa.alipay.com", 532 | "status": "valid", 533 | "package_open_type": "APPLY" 534 | } 535 | ] 536 | } 537 | }`) 538 | }) 539 | 540 | got, err := client.Mini.QueryVersionDetail(context.Background(), &QueryVersionDetailBiz{ 541 | AppVersion: "0.0.1", 542 | BundleID: "com.alipay.alipaywallet", 543 | }) 544 | if err != nil { 545 | t.Errorf("Mini.QueryVersionDetail returned unexcepted error: %v", err) 546 | } 547 | want := &VersionDetail{ 548 | AppVersion: "0.0.1", 549 | AppName: "小程序demo", 550 | AppEnglishName: "demo example", 551 | AppLogo: "http://image.aaaa.alipay.com", 552 | VersionDesc: "这是一个简单的版本描述", 553 | GrayStrategy: "p10", 554 | Status: "INIT", 555 | RejectReason: "名称太宽泛", 556 | ScanResult: "True", 557 | GmtCreate: "2017-12-12 12:00:00", 558 | GmtApplyAudit: "2017-12-12 12:00:00", 559 | GmtOnline: "2017-12-12 12:00:00", 560 | GmtOffline: "2017-12-12 12:00:00", 561 | GmtAuditEnd: "2017-12-12 12:00:00", 562 | AppDesc: "小程序demo的相关示例", 563 | ServiceRegionType: "LOCATION", 564 | ServiceRegionInfo: []*RegionInfo{ 565 | { 566 | ProvinceCode: "310000", 567 | ProvinceName: "浙江省", 568 | CityCode: "310000", 569 | CityName: "杭州市", 570 | AreaCode: "311100", 571 | AreaName: "余杭区", 572 | }, 573 | }, 574 | ScreenShotList: []string{"http://image.aaa.alipay.com"}, 575 | AppSlogan: "小程序demo简介", 576 | Memo: "这是一个demo示例", 577 | ServicePhone: "13110101010", 578 | ServiceEmail: "example@mail.com", 579 | MiniAppCategoryInfoList: []*MiniAppCategoryInfo{ 580 | { 581 | FirstCategoryID: "1234", 582 | FirstCategoryName: "生活服务", 583 | SecondCategoryID: "12344", 584 | SecondCategoryName: "汽车服务", 585 | }, 586 | }, 587 | PackageInfoList: []*MiniPackageInfo{ 588 | { 589 | PackageName: "基础能力", 590 | PackageDesc: "这是通用能力", 591 | DocURL: "http://doc.aaa.alipay.com", 592 | Status: "valid", 593 | PackageOpenType: "APPLY", 594 | }, 595 | }, 596 | } 597 | if !reflect.DeepEqual(got, want) { 598 | t.Errorf("Mini.QueryVersionDetail got %+v, want %+v", got, want) 599 | } 600 | } 601 | 602 | func TestMiniService_QueryVersionDetail_failed(t *testing.T) { 603 | client, mux, _, tearDown := setup() 604 | defer tearDown() 605 | 606 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 607 | 608 | fmt.Fprint(w, `{ 609 | "alipay_open_mini_version_detail_query_response": { 610 | "code": "20000", 611 | "msg": "Service Currently Unavailable", 612 | "sub_code": "isp.unknow-error", 613 | "sub_msg": "系统繁忙" 614 | } 615 | }`) 616 | }) 617 | _, err := client.Mini.QueryVersionDetail(context.Background(), &QueryVersionDetailBiz{ 618 | AppVersion: "0.0.1", 619 | BundleID: "com.alipay.alipaywallet", 620 | }) 621 | if err == nil { 622 | t.Errorf("Mini.OnlineVersion excepted error") 623 | } 624 | } 625 | 626 | func TestMiniService_RollbackVersion(t *testing.T) { 627 | client, mux, _, tearDown := setup() 628 | defer tearDown() 629 | 630 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 631 | 632 | fmt.Fprint(w, `{ 633 | "alipay_open_mini_version_rollback_response": { 634 | "code": "10000", 635 | "msg": "Success" 636 | } 637 | }`) 638 | }) 639 | 640 | err := client.Mini.RollbackVersion(context.Background(), &RollbackVersionBiz{ 641 | AppVersion: "0.0.1", 642 | BundleID: "com.alipay.alipaywallet", 643 | }) 644 | if err != nil { 645 | t.Errorf("Mini.RollbackVersion returned unexcepted error: %v", err) 646 | } 647 | } 648 | 649 | func TestMiniService_RollbackVersion_failed(t *testing.T) { 650 | client, mux, _, tearDown := setup() 651 | defer tearDown() 652 | 653 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 654 | 655 | fmt.Fprint(w, `{ 656 | "alipay_open_mini_version_rollback_response": { 657 | "code": "20000", 658 | "msg": "Service Currently Unavailable", 659 | "sub_code": "isp.unknow-error", 660 | "sub_msg": "系统繁忙" 661 | } 662 | }`) 663 | }) 664 | err := client.Mini.RollbackVersion(context.Background(), &RollbackVersionBiz{ 665 | AppVersion: "0.0.1", 666 | BundleID: "com.alipay.alipaywallet", 667 | }) 668 | if err == nil { 669 | t.Errorf("Mini.RollbackVersion excepted error") 670 | } 671 | } 672 | 673 | func TestMiniService_UploadVersion(t *testing.T) { 674 | client, mux, _, tearDown := setup() 675 | defer tearDown() 676 | 677 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 678 | 679 | fmt.Fprint(w, `{ 680 | "alipay_open_mini_version_upload_response": { 681 | "code": "10000", 682 | "msg": "Success" 683 | } 684 | }`) 685 | }) 686 | 687 | err := client.Mini.UploadVersion(context.Background(), &UploadVersionBiz{ 688 | AppVersion: "0.0.1", 689 | BundleID: "com.alipay.alipaywallet", 690 | TemplateID: "1", 691 | TemplateVersion: "0.0.1", 692 | Ext: "{\"extEnable\": true, \"extPages\": {\"pages/face/index\": {\"defaultTitle\": \"哈哈哈哈\"}},\"window\": {\"defaultTitle\": \"AI2\"}}", 693 | }) 694 | if err != nil { 695 | t.Errorf("Mini.UploadVersion returned unexcepted error: %v", err) 696 | } 697 | } 698 | 699 | func TestMiniService_UploadVersion_failed(t *testing.T) { 700 | client, mux, _, tearDown := setup() 701 | defer tearDown() 702 | 703 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 704 | 705 | fmt.Fprint(w, `{ 706 | "alipay_open_mini_version_upload_response": { 707 | "code": "20000", 708 | "msg": "Service Currently Unavailable", 709 | "sub_code": "isp.unknow-error", 710 | "sub_msg": "系统繁忙" 711 | } 712 | }`) 713 | }) 714 | err := client.Mini.UploadVersion(context.Background(), &UploadVersionBiz{ 715 | AppVersion: "0.0.1", 716 | BundleID: "com.alipay.alipaywallet", 717 | }) 718 | if err == nil { 719 | t.Errorf("Mini.UploadVersion excepted error") 720 | } 721 | } 722 | 723 | func TestMiniService_QueryVersionList(t *testing.T) { 724 | client, mux, _, tearDown := setup() 725 | defer tearDown() 726 | 727 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 728 | 729 | fmt.Fprint(w, `{ 730 | "alipay_open_mini_version_list_query_response": { 731 | "code": "10000", 732 | "msg": "Success", 733 | "app_versions": [ 734 | "0.0.1" 735 | ] 736 | } 737 | }`) 738 | }) 739 | 740 | got, err := client.Mini.QueryVersionList(context.Background()) 741 | if err != nil { 742 | t.Errorf("Mini.QueryVersionList returned unexcepted error: %v", err) 743 | } 744 | want := &QueryVersionListResp{ 745 | AppVersions: []string{"0.0.1"}, 746 | } 747 | if !reflect.DeepEqual(got, want) { 748 | t.Errorf("Mini.QueryVersionList got %+v, want %+v", got, want) 749 | } 750 | } 751 | 752 | func TestMiniService_QueryVersionList_failed(t *testing.T) { 753 | client, mux, _, tearDown := setup() 754 | defer tearDown() 755 | 756 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 757 | 758 | fmt.Fprint(w, `{ 759 | "alipay_open_mini_version_list_query_response": { 760 | "code": "20000", 761 | "msg": "Service Currently Unavailable", 762 | "sub_code": "isp.unknow-error", 763 | "sub_msg": "系统繁忙" 764 | } 765 | }`) 766 | }) 767 | _, err := client.Mini.QueryVersionList(context.Background()) 768 | if err == nil { 769 | t.Errorf("Mini.QueryVersionList excepted error") 770 | } 771 | } 772 | 773 | func TestMiniService_QueryVersionBuild(t *testing.T) { 774 | client, mux, _, tearDown := setup() 775 | defer tearDown() 776 | 777 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 778 | 779 | fmt.Fprint(w, `{ 780 | "alipay_open_mini_version_build_query_response": { 781 | "code": "10000", 782 | "msg": "Success", 783 | "need_rotation": "true", 784 | "create_status": "6" 785 | } 786 | }`) 787 | }) 788 | 789 | got, err := client.Mini.QueryVersionBuild(context.Background(), &QueryVersionBuildBiz{ 790 | AppVersion: "0.0.1", 791 | BundleID: "com.alipay.alipaywallet", 792 | }) 793 | if err != nil { 794 | t.Errorf("Mini.QueryVersionBuild returned unexcepted error: %v", err) 795 | } 796 | want := &QueryVersionBuildResp{ 797 | NeedRotation: "true", 798 | CreateStatus: "6", 799 | } 800 | if !reflect.DeepEqual(got, want) { 801 | t.Errorf("Mini.QueryVersionBuild got %+v, want %+v", got, want) 802 | } 803 | } 804 | 805 | func TestMiniService_QueryVersionBuild_failed(t *testing.T) { 806 | client, mux, _, tearDown := setup() 807 | defer tearDown() 808 | 809 | mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 810 | 811 | fmt.Fprint(w, `{ 812 | "alipay_open_mini_version_build_query_response": { 813 | "code": "20000", 814 | "msg": "Service Currently Unavailable", 815 | "sub_code": "isp.unknow-error", 816 | "sub_msg": "系统繁忙" 817 | } 818 | }`) 819 | }) 820 | _, err := client.Mini.QueryVersionBuild(context.Background(), &QueryVersionBuildBiz{ 821 | AppVersion: "0.0.1", 822 | BundleID: "com.alipay.alipaywallet", 823 | }) 824 | if err == nil { 825 | t.Errorf("Mini.QueryVersionBuild excepted error") 826 | } 827 | } 828 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Cluas/go-alipay 2 | 3 | go 1.14 4 | --------------------------------------------------------------------------------