Future Tech Blog
フューチャー技術ブログ

Zuora連載2:Zuora REST API 利用と開発環境構築


概要

Zuora Central Platform には様々なリソースがありますが、それらは API 越しに扱うことができます。
この記事では、Zuora REST API の簡単な利用方法と Swagger を利用したモックを作成して、開発環境を整備することを扱います。

Zuora Central Platformとは?

Version

  • Zuora REST API
    • version: 2020-04-08
  • go-swagger
    • v0.23.0

Zuora REST API Reference

Zuora Central Platform の API 仕様は下記で参照することができます。
https://www.zuora.com/developer/api-reference/#

Zuora Object Model

Zuora Central Platform の API を扱う上で、リソース同士の関係性が知りたいケースはよくあります。
リソースの関係性は図として公開されています。

https://www.zuora.com/developer/api-reference/#section/Zuora-Object-Model

API

各リソースごとに、CRUD が定義されているようなイメージを持っていただければ良さそうです。
種類としては 2 つほどあり、CRUD: とプレフィクスがついている API は単純な CRUD 処理、その他は関連リソース含めて扱えるような API となっています。

よく利用する API

現在利用しているプロジェクトでは、以下の API をよく利用しています。

  • Create Account
    • POST /v1/accounts
    • サブスクリプション契約を行うアカウントを新規作成する API
  • Create subscription
    • POST /v1/subscriptions
    • サブスクリプション契約を実施する API
  • Get subscriptions by account
    • GET /v1/subscriptions
    • アカウント別のサブスクリプション情報を取得する API
  • Post usage
    • POST /v1/usage
    • 利用量を Zuora へ連携する API
    • csv を HTTP Body へセットしてリクエストを実施する

同時リクエスト制限

Zuora REST API は基本的には、同時リクエスト数の上限が 40 となっています。
実際は、APIやデータサイズ別に、リクエスト数の上限が以下に整理されてますので、システム構築の際は一度目を通しておいたほうが良いです。

Concurrent Request Limits - Zuora

Zuora REST API リクエスト環境

下記の環境が提供されております。
いづれも API 利用のため、Zuora Central Platform へログインを実施し、クレデンシャルを発行する必要があります。
Sandbox 環境はテスト用として利用できる環境になっています。

https://www.zuora.com/developer/api-reference/#section/Introduction/Access-to-the-API

リクエスト方法

ここで、Postman を利用したリクエスト方法について記載します。

認証は OAuth2.0 を利用して行われています。 設定手順も、ドキュメントにまとまっています。
https://www.zuora.com/developer/api-reference/#section/Authentication/OAuth-v2.0

  1. クレデンシャルを発行: Zuora Central Platform 管理ページよりクレデンシャルを作成します。
    • 1.1. メニューより、「管理者」を選択

    • 1.2. 「ユーザーの管理」を選択
    • 1.3. 「OAuth クライアント」から新規クライアントを作成し、 client_idclient_secret を保存します。
  2. OAuth API をリクエストして Bearer トークンを取得
    • 2.1. Postman で新規リクエスト作成から、「Authorization」を選択
    • Type を 「OAuth 2.0」へ設定し、「Get New Access Token」を押下します。
    • 2.2. 「Access Token URL」を下記 Generate an OAuth token URL に設定し、 client_idclient_secret も合わせてセットし、「Request Token」を押下します。
      https://rest.apisandbox.zuora.com/oauth/token
    • 2.3. アクセストークンが作成されます。
  3. Bearer トークンを Authorization ヘッダーに付与してリクエスト
    Create Account へリクエストしてみます。
    https://rest.apisandbox.zuora.com/v1/accounts
    • 3.1. 先ほど作成したトークンを指定します。
    • 3.2. URL とサンプルの Body をセットし、リクエストを実行します。
    • 3.3. 以下のように返却されると成功です。

Swagger

Zuora REST API 定義は Swagger ファイルとしても、公開されています。
下記リンクよりダウンロードが可能です。

Swagger 2.0
https://assets.zuora.com/zuora-documentation/swagger.yaml

Open API(Swagger) 3.0
https://assets.zuora.com/zuora-documentation/swagger3.yaml

今回は、この Swagger ファイルを利用してモックサーバーとクライアントを作成して開発ができるような環境を作ります。

モックサーバーの作成

モックサーバーは Prism を利用します。
詳細は、下記記事に記載があります。とても便利です。

node の環境が整っている方はこちら

1
2
3
4
5
# install
npm install -g @stoplight/prism-cli

# モック起動
prism mock -p 4010 ./swagger.yaml

docker から利用したい場合はこちら

1
2
# モック起動
docker run --init --rm -it -p 4010:4010 -v $(pwd):/tmp -P stoplight/prism:3 mock -h 0.0.0.0 "/tmp/swagger.yaml"

ここで試しにリクエストしてみます。
Prism はバリデーション等もしてくれるので、Swagger 定義に沿ったリクエストをする必要があります。

例: アカウント登録リクエストサンプル

1
2
3
4
5
6
7
8
9
10
% curl -X POST "http://localhost:4010/v1/accounts" -H "Content-Type: application/json" -d '{"name": "test", "currency": "JPY", "billToContact": {"lastName": "Taro", "firstName":"Tanaka" }}' | jq .

{
"success": true,
"accountId": "402892c74c9193cd014c96bbe7c101f9",
"accountNumber": "A00000004",
"billToContactId": "2c92c8fb68a28d180168a7ccedba1c4c",
"soldToContactId": "2c92c8fb68a28d180168a7ccedc61c4e",
"paymentMethodId": "402892c74c9193cd014c96bbe7d901fd"
}

Get リクエスト時のエラー解消方法

ここで少々本筋とはずれますが、Get リクエストをした際に下記エラーに遭遇するかもしれないです。パスパラメータに、パラメータを渡しているにもかかわらず未定義であるかのようなエラーです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# アカウント取得API
# /v1/accounts/{account-key}
% curl -s -X GET "http://localhost:4010/v1/accounts/xxxxx" | jq .
{
"type": "https://stoplight.io/prism/errors#UNPROCESSABLE_ENTITY",
"title": "Invalid request body payload",
"status": 422,
"detail": "Your request is not valid and no HTTP validation response was found in the spec, so Prism is generating this error for you.",
"validation": [
{
"location": [
"path"
],
"severity": "Error",
"code": "required",
"message": "should have required property 'account-key'"
}
]
}

こちらは、Prism 側の問題で、パスパラメータの - を取り除くことで解消します。
具体的には、パラメータキーの account-keyaccountKey へ修正します。

swagger.yml

1
2
3
4
5
6
7
8
9
10
11
12

# before
/v1/accounts/{account-key}:
# after
/v1/accounts/{accountKey}:
get:
parameters:
# before
- name: account-key
# after
- name: accountKey
in: path

修正後はリクエストが成功するようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% curl -s -X GET "http://localhost:4010/v1/accounts/xxxxx" | jq .
{
"basicInfo": {
# 省略
},
"billingAndPayment": {
# 省略
},
"metrics": {
# 省略
},
"billToContact": {
# 省略
},
"soldToContact": {
# 省略
},
"success": true
}

クライアントの作成

コード生成

Zuora REST API へのリクエストですが、フロントエンドから直接というケースより、バックエンド内で呼び出すケースが多いのではないでしょうか。
(実際、私が利用しているケースも同様で、バックエンド内で利用しています。)
そのため、クライアントは バックエンドで利用される Go で作成します。

Swagger のクライアント生成は、 go-swagger を利用します。
115552 行 ( ※ version: 2020-04-08 ) ほど Swagger ファイルがあり 3 分程度生成に時間がかかりました。
validation を実行すると、エラーで引っかかってしまうため、仕方なくスキップしています。

1
2
3
4
5
# ./zuoraClient ディレクトリ以下に生成
swagger generate client -q \
-f ./swagger.yaml \
--skip-validation \
-t ./zuoraClient

補足: エラー修正

上記コマンド実行にあたり、下記エラーが発生しました。

1
failed rendering template data for definition: template execution failed for template definition: template: schemavalidator:491:65: executing "schemavalidator" at <.>: wrong type for value; expected string; got bool

template を展開する際に、型エラーになっているようです。
これだけだとよくわからなかったので、 go-swagger 側のソースコードを読み、エラーの箇所を突き止めました。
https://github.com/go-swagger/go-swagger/blob/v0.23.0/generator/templates/schemavalidator.gotmpl#L491

前後のコードとエラーログから、 Enum 定義で type: string を期待しているが bool 型の値が定義されていると読み取れました。
そこで、swagger.yml 内を grep して検索したところ、下記の定義が見つかりました。

1
2
3
4
5
6
7
8
9
10
11
linkage_type:
enum:
- Start
- Success
- Failure
- Iterate
- true
- false
- Approve
- Reject
type: string

こちらを、以下のように定義し直すことでエラーが回避できました。

1
2
3
4
5
6
7
8
9
10
11
linkage_type:
enum:
- Start
- Success
- Failure
- Iterate
- "true"
- "false"
- Approve
- Reject
type: string

クライアントの利用

クライアントコードは以下のように利用できます。
ここでは、 Get subscriptions by account をリクエストしています。
注意点として、処理の成功がステータスコードではなく success プロパティで表現されるケースがあるので、エラーハンドリングを入れています。

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"log"

"github.com/taga_sandbox/zuora_api_sandbox/zuoraClient/client"
"github.com/taga_sandbox/zuora_api_sandbox/zuoraClient/client/subscriptions"
)

func main() {
// モックサーバーへ接続するクライアント作成
subscriptionClient := client.NewHTTPClientWithConfig(nil, &client.TransportConfig{
Host: "localhost:4010",
BasePath: "/",
Schemes: []string{"http"},
}).Subscriptions

params := subscriptions.NewGETSubscriptionsByAccountParams()
params.SetAccountKey("dummy-account-key")

res, err := subscriptionClient.GETSubscriptionsByAccount(params)
if err != nil {
log.Fatalln(err)
}

// 処理が成功したかどうかは success プロパティで判定する
if !res.GetPayload().Success {
log.Fatalln("falied")
}

fmt.Println(res)
}

実行

1
2
❯ go run main.go
[GET /v1/subscriptions/accounts/{accountKey}][200] gETSubscriptionsByAccountOK &{NextPage: Subscriptions:[0xc0005ce2c0] Success:true}

これで、モックサーバーを立ち上げ、かつクライアントからのリクエストを実行することができました。
このセットが用意されていると、ローカルでの開発がとても楽になります。

補足: エラーと解消方法

go-swagger で生成したコードをそのまま利用すると、エラーになってハマるケースがありましたので、解消法を記載します。
このあたりは、もうちょっと楽にできたらいいなぁと思います。

  1. cannot convert XXX (type float64) to type string エラー

go-swagger で生成されたコード自体に問題があるケースです。

1
2
3
4
5
6
7
8
9
10
❯ go run main.go
# github.com/taga_sandbox/zuora_api_sandbox/zuoraClient/models
zuoraClient/models/proxy_create_payment.go:567:55: cannot convert m.Amount (type float64) to type string
zuoraClient/models/proxy_create_payment.go:571:55: cannot convert m.Amount (type float64) to type string
zuoraClient/models/proxy_create_payment.go:584:75: cannot convert m.AppliedCreditBalanceAmount (type float64) to type string
zuoraClient/models/proxy_create_payment.go:588:75: cannot convert m.AppliedCreditBalanceAmount (type float64) to type string
zuoraClient/models/proxy_create_payment.go:601:69: cannot convert m.AppliedInvoiceAmount (type float64) to type string
zuoraClient/models/proxy_create_payment.go:605:69: cannot convert m.AppliedInvoiceAmount (type float64) to type string
zuoraClient/models/proxy_modify_payment.go:315:55: cannot convert m.Amount (type float64) to type string
zuoraClient/models/proxy_modify_payment.go:319:55: cannot convert m.Amount (type float64) to type string

生成コードのエラー該当行

zuoraClient/models/proxy_create_payment.go 561 行目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func (m *ProxyCreatePayment) validateAmount(formats strfmt.Registry) error {

if swag.IsZero(m.Amount) { // not required
return nil
}

// この行がエラー
if err := validate.MinLength("Amount", "body", string(m.Amount), 0); err != nil {
return err
}

// この行がエラー
if err := validate.MaxLength("Amount", "body", string(m.Amount), 16); err != nil {
return err
}

return nil
}

swagger.yml での該当行は下記
type: number に対して minLength , maxLength を定義していることが原因のようです。

1
2
3
4
5
6
7
8
9
10
11
12
13
ProxyCreatePayment:
allOf:
- properties:
# 省略

Amount:
description: "The amount of the payment.

"
format: double
maxLength: 16
minLength: 0
type: number

ここは迷ったのですが、生成コード側に手を入れることにしました。
この後の利用でも生成コードと、Zuora REST API のレスポンスで型が不整合とのエラーが一定数起きたので、生成コードの修正は可としています。

修正後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  func (m *ProxyCreatePayment) validateAmount(formats strfmt.Registry) error {

if swag.IsZero(m.Amount) { // not required
return nil
}

if err := validate.MinLength("Amount", "body", fmt.Sprint(m.Amount), 0); err != nil { //この行がエラー
return err
}

if err := validate.MaxLength("Amount", "body", fmt.Sprint(m.Amount), 16); err != nil { //この行がエラー
return err
}

return nil
}
  1. none of producers エラー

クライアントコードからリクエストを実行すると、下記エラーが発生します。
こちらも、 go-swagger 側の問題で Content-Typecharset に対応していないことが原因です。

1
2
3
❯ go run main.go
2020/04/21 15:00:00 none of producers: map[application/json:0x13e1270 application/octet-stream:0x13dfeb0 application/xml:0x13e2040 text/csv:0x13e0c40 text/html:0x13e18f0 text/plain:0x13e18f0] registered. try application/json; charset=utf-8
exit status 1

swagger.yml で以下のように定義されているため修正して、再度 client を生成します。

1
2
3
4
5
6
7
8
9
10
11
12

# before
produces:
- application/json; charset=utf-8
consumes:
- application/json; charset=utf-8

# after
produces:
- application/json
consumes:
- application/json

client だけ 再生成

1
2
3
4
5
swagger generate client -q \
-f ./swagger.yaml \
--skip-validation \
--skip-models \
-t ./zuoraClient
  1. cannot unmarshal number into Go struct エラー
1
2
3
❯ go run main.go
2020/04/21 15:00:00 json: cannot unmarshal number into Go struct field GETSubscriptionWrapper.subscriptions of type string
exit status 1

Zuora REST API モックサーバーからの JSON レスポンスを Go の Struct へパースしようとしたところ、型エラーが発生してました。
このエラーは結構、対象を見つけ出すのが大変です。
モックサーバーのレスポンスと 生成 Struct の型定義を見比べて、原因を調査します。
(※ 実際の開発の際は、モックサーバーではなく Zuora REST API からのレスポンスと比較してください。)

今回は、 以下 2 点が原因でした
型を変更すると付随でコンパイルエラーになるため、合わせて修正します。

  • string型 に対して bool値true/false が返却
  • string型 に対して number が返却

./zuoraClient/models/g_e_t_subscription_type.go

1
2
3
4
5
6
7
8
9
10
type GETSubscriptionType struct {

// 省略

// before
InvoiceSeparately string `json:"invoiceSeparately,omitempty"`

// after
InvoiceSeparately bool `json:"invoiceSeparately,omitempty"`
}

zuoraClient/models/g_e_t_subscription_rate_plan_charges_type.go

1
2
3
4
5
6
7
8
9
10
11
12
type GETSubscriptionRatePlanChargesType struct {

// 省略

// before
Dmrc string `json:"dmrc,omitempty"`

// after
Dmrc float64 `json:"dmrc,omitempty"`

// Dtcv, Mrr, Tcvh も同様
}

終わりに

Zuora REST API のリクエスト方法と、開発環境整備のためのモックサーバーとクライアントコードの生成方法について記載しました。
若干ハマりどころはありますが、整備すると開発とテストが格段にやりやすくなります。

こちらを参考に、Zuora REST API を利用した開発効率が上がれば良いなと思います。