APIチュートリアル

このチュートリアルでは、サブスクリプション機能のAPIを利用した実装について説明します。

なお、この章では環境準備及び認証処理についてはすでに完了しているものとします。

APIリファレンスについては XG APIリファレンス を、エラーコード一覧については エラーコード一覧表 を参照してください。

サブスクリプション購入

サブスクリプション商品の購入処理を行います。

購入処理は「サブスクリプション購入検証API」と「サブスクリプション購入API」の2段階で行います。
サブスクリプション購入APIのリクエスト内でXGサブスクリプション通知が送信され、ゲームサーバーで利用権の付与を行います。
XGサブスクリプション通知のレスポンスで指定したサブスクリプションタグに応じて、ゲーム内通貨が発行されます。
XGサブスクリプション通知の詳細については XGサブスクリプション通知 を参照してください。

App Store

ストア「App Store」でサブスクリプション商品を購入したことをXGに登録します。

App Storeで購入処理をした際にストアから発行されたトランザクションID(TransactionID)をXGへリクエストしてください。
購入検証APIのレスポンスにより、新規購入の場合は購入APIを、継続の場合は継続APIを呼び出します。
<?php

// リクエストの準備
$storeId = "appstore";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someTransactionId';     // ストアから発行されたトランザクションID

$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/subscription/v1/users/%s/stores/%s/purchase", $userId, $storeId); // APIのパス
$body = json_encode([
    'transactionId' => $transactionId,
    'productId' => $productId,
]);

try {
    $response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        error_log(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
        throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
    }
    if (isset($res->balance)) {
        foreach ($res->balance as $currencyId => $balance) {
            printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
        }
    }
} else {
    // エラー処理
}
// リクエスト用の構造体
type Body struct {
	TransactionId string `json:"transactionId"`
	ProductId     string `json:"productId"`
}

// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgPurchaseStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
	Added         map[string]XgBalanceDetail `json:"added"`
}

// リクエストの準備
storeId := "appstore"

productId := "someProductId";     // 商品ID
transactionId := "someTransactionId";     // ストアから発行されたトランザクションID

method := http.MethodPut                                                             // HTTPメソッドの種類
path := fmt.Sprintf("/subscription/v1/users/%v/stores/%v/purchase", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
	TransactionId: transactionId,
	ProductId:     productId,
})
if err != nil {
	panic(err)
}
body := string(requestBodyBytes)

response, err := xgApi.SendRequest(method, path, body)
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", path, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgPurchaseStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		log.Printf("JSON Unmarshal error: %v, response: %v", err, string(bodyBytes))
		return err
	}
	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

Google Play

ストア「Google Play」でサブスクリプション商品を購入したことをXGに登録します。

Google Playで購入処理をした際にストアから発行された購入トークン(PurchaseToken)をTransactionIDとしてXGへリクエストしてください。
<?php

// リクエストの準備
$storeId = "googleplay";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someToken';     // 購入トークン

$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/subscription/v1/users/%s/stores/%s/purchase", $userId, $storeId); // APIのパス
$body = json_encode([
    'transactionId' => $transactionId,
    'productId' => $productId,
]);

try {
    $response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        error_log(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
        throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
    }
    if (isset($res->balance)) {
        foreach ($res->balance as $currencyId => $balance) {
            printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
        }
    }
} else {

}
// リクエスト用の構造体
type Body struct {
	TransactionId string `json:"transactionId"`
	ProductId     string `json:"productId"`
}

// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgPurchaseStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Status        string                     `json:"status"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
	Added         map[string]XgBalanceDetail `json:"added"`
}

// リクエストの準備
storeId := "googleplay"

productId := "someProductId";     // 商品ID
transactionId := "someToken";     // 購入トークン

method := http.MethodPut                                                             // HTTPメソッドの種類
path := fmt.Sprintf("/subscription/v1/users/%v/stores/%v/purchase", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
	TransactionId: transactionId,
	ProductId:     productId,
})
if err != nil {
	panic(err)
}
body := string(requestBodyBytes)

response, err := xgApi.SendRequest(method, path, body)
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", path, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgPurchaseStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		log.Printf("JSON Unmarshal error: %v, response: %v", err, string(bodyBytes))
		return err
	}
	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

DMM GAMES

ストア「DMM GAMES」でサブスクリプション商品を購入したことをXGに登録します。

DMM GAMESで購入処理をした際にストアから発行された購入トークン(PurchaseToken)をTransactionIDとしてXGへリクエストしてください。
<?php

// リクエストの準備
$storeId = "dmmgames";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someToken';     // 購入トークン

$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/subscription/v1/users/%s/stores/%s/purchase", $userId, $storeId); // APIのパス
$body = json_encode([
	'transactionId' => $transactionId,
	'productId' => $productId,
	'games' => [
		'appId' => $gamesAppId, // ゲームID
		'device' => $device, // デバイス(android, ios, win, mac, pc_web, sp_web)
		'userType' => $userType, // ユーザータイプ(空文字, staff, developer)
	],
]);

try {
	$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
	errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s (%s) : HTTP status: %d\n", $path, $device, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
	$res = json_decode($bodyBytes);
	if (is_null($res)) {
		// エラー
		$err = json_last_error_msg();
		error_log(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
		throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
	}
	if (isset($res->balance)) {
		foreach ($res->balance as $currencyId => $balance) {
			printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
		}
	}
} else {
	errorResponse($bodyBytes);
}
// リクエスト用の構造体
type Games struct {
	AppId    string `json:"appId"`
	Device   string `json:"device"`
	UserType string `json:"userType"`
}

type Body struct {
	TransactionId string `json:"transactionId"`
	ProductId     string `json:"productId"`
	Games         Games  `json:"games"`
}

// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgPurchaseStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Status        string                     `json:"status"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
	Added         map[string]XgBalanceDetail `json:"added"`
}

// リクエストの準備
storeId := "dmmgames"

transactionId := "someToken" // 購入トークン
productId := "someProductId" // 商品ID

method := http.MethodPut                                                             // HTTPメソッドの種類
path := fmt.Sprintf("/subscription/v1/users/%v/stores/%v/purchase", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
	TransactionId: transactionId,
	ProductId:     productId,
	Games: Games{
		AppId:    gamesAppId, // ゲームID
		Device:   device,     // デバイス(android, ios, win, mac, pc_web, sp_web)
		UserType: userType,   // ユーザータイプ(空文字, staff, developer)
	},
})
if err != nil {
	panic(err)
}
body := string(requestBodyBytes)

response, err := xgApi.SendRequest(method, path, body)
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s (%s) : HTTP status: %v\n", path, device, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgPurchaseStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		log.Printf("JSON Unmarshal error: %v, response: %v", err, string(bodyBytes))
		return err
	}
	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

サブスクリプション継続(App Store)

App Storeでサブスクリプションが継続された際に、ゲーム端末の未処理レシートを処理します。

App Storeではサブスクリプションの継続時に新しいトランザクションID(TransactionID)が発行されます。
ゲーム端末で未処理のレシートを検出した場合、このAPIで継続処理を行います。
リクエスト内でXGサブスクリプション通知が送信され、ゲームサーバーで利用権の付与を行います。
XGサブスクリプション通知の詳細については XGサブスクリプション通知 を参照してください。
<?php

// リクエストの準備
$storeId = "appstore";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someTransactionId';     // ストアから発行されたトランザクションID

$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/subscription/v1/users/%s/stores/%s/renewal", $userId, $storeId); // APIのパス
$body = json_encode([
    'transactionId' => $transactionId,
    'productId' => $productId,
]);

try {
    $response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        error_log(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
        throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
    }
    if (isset($res->balance)) {
        foreach ($res->balance as $currencyId => $balance) {
            printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
        }
    }
} else {
    // エラー処理
}
// リクエスト用の構造体
type Body struct {
	TransactionId string `json:"transactionId"`
	ProductId     string `json:"productId"`
}

// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgPurchaseStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
	Added         map[string]XgBalanceDetail `json:"added"`
}

// リクエストの準備
storeId := "appstore"

productId := "someProductId";     // 商品ID
transactionId := "someTransactionId";     // ストアから発行されたトランザクションID

method := http.MethodPut                                                            // HTTPメソッドの種類
path := fmt.Sprintf("/subscription/v1/users/%v/stores/%v/renewal", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
	TransactionId: transactionId,
	ProductId:     productId,
})
if err != nil {
	panic(err)
}
body := string(requestBodyBytes)

response, err := xgApi.SendRequest(method, path, body)
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", path, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgPurchaseStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		log.Printf("JSON Unmarshal error: %v, response: %v", err, string(bodyBytes))
		return err
	}
	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

サブスクリプション購入検証

サブスクリプション商品の購入を検証します。

App Store

ストア「App Store」でサブスクリプション商品の購入を検証します。

App Storeで購入処理をした際にストアから発行されたトランザクションID(TransactionID)をXGへリクエストしてください。
<?php

// リクエストの準備
$storeId = "appstore";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someTransactionId';     // ストアから発行されたトランザクションID

$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/verify/v1/users/%s/stores/%s/subscription-products/%s/transactions/%s", $userId, $storeId, $productId, $transactionId); // APIのパス

try {
    $response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        throw new \Exception(sprintf("JSON Decode error: %s", $err));
    }

    printf("ステータス: %s\n", $res->status);

    if (isset($res->balance)) {
        foreach ($res->balance as $currencyId => $balance) {
            printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
        }
    }
} else {
    // エラー処理
}
// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgVerifyStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Status        string                     `json:"status"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
}

// リクエストの準備
storeId := "appstore"

transactionId := "someToken" // 購入トークン
productId := "someProductId" // 商品ID

method := http.MethodGet                                                                                                                 // HTTPメソッドの種類
path := fmt.Sprintf("/verify/v1/users/%s/stores/%s/subscription-products/%s/transactions/%s", userId, storeId, productId, transactionId) // APIのパス

response, err := xgApi.SendRequest(method, path, "")
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", path, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgVerifyStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		panic(err)
	}

	fmt.Printf("ステータス: %s\n", res.Status)

	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

Google Play

ストア「Google Play」でサブスクリプション商品の購入を検証します。

Google Playで購入処理をした際にストアから発行された購入トークン(PurchaseToken)をTransactionIDとしてXGへリクエストしてください。
<?php

// リクエストの準備
$storeId = "googleplay";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someToken';     // 購入トークン

$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/verify/v1/users/%s/stores/%s/subscription-products/%s/transactions/%s", $userId, $storeId, $productId, $transactionId); // APIのパス

try {
    $response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        throw new \Exception(sprintf("JSON Decode error: %s", $err));
    }

    printf("ステータス: %s\n", $res->status);

    if (isset($res->balance)) {
        foreach ($res->balance as $currencyId => $balance) {
            printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
        }
    }
} else {
    // エラー処理
}
// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgVerifyStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Status        string                     `json:"status"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
}

// リクエストの準備
storeId := "googleplay"

transactionId := "someToken" // 購入トークン
productId := "someProductId" // 商品ID

method := http.MethodGet                                                                                                                     // HTTPメソッドの種類
basePath := fmt.Sprintf("/verify/v1/users/%s/stores/%s/subscription-products/%s/transactions/%s", userId, storeId, productId, transactionId) // APIのパス
pathUrl, err := url.Parse(basePath)
if err != nil {
	panic(err)
}

// クエリパラメータの設定
param := url.Values{}
pathUrl.RawQuery = param.Encode()
path := pathUrl.String()

response, err := xgApi.SendRequest(method, path, "")
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", path, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgVerifyStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		panic(err)
	}

	fmt.Printf("ステータス: %s\n", res.Status)

	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

DMM GAMES

ストア「DMM GAMES」でサブスクリプション商品の購入を検証します。

DMM GAMESで購入処理をした際にストアから発行された購入トークン(PurchaseToken)をTransactionIDとしてXGへリクエストしてください。
<?php

// リクエストの準備
$storeId = "dmmgames";

$productId = 'someProductId';     // 商品ID
$transactionId = 'someToken';     // 購入トークン

$param = [];
$param["appId"] = $gamesAppId; // GAMESアプリID
$param["device"] = $device; // デバイス(android, ios, win, mac, pc_web, sp_web)
$param["userType"] = $userType; // ユーザータイプ(空文字, staff, developer)

$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/verify/v1/users/%s/stores/%s/subscription-products/%s/transactions/%s?%s", $userId, $storeId, $productId, $transactionId, http_build_query($param)); // APIのパス

try {
	$response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
	errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s (%s) : HTTP status: %d\n", $path, $device, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
	$res = json_decode($bodyBytes);
	if (is_null($res)) {
		// エラー
		$err = json_last_error_msg();
		throw new \Exception(sprintf("JSON Decode error: %s", $err));
	}

	printf("ステータス (%s): %s\n", $device, $res->status);

	if (isset($res->balance)) {
		foreach ($res->balance as $currencyId => $balance) {
			printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
		}
	}
} else {
	// エラー処理
	errorResponse($bodyBytes);
}
// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type XgVerifyStoreResponse struct {
	TransactionId string                     `json:"transactionId"`
	TransactionAt time.Time                  `json:"transactionAt"`
	Quantity      int64                      `json:"quantity"`
	Status        string                     `json:"status"`
	Balance       map[string]XgBalanceDetail `json:"balance"`
}

// リクエストの準備
storeId := "dmmgames"

transactionId := "someToken" // 購入トークン
productId := "someProductId" // 商品ID

method := http.MethodGet                                                                                                                     // HTTPメソッドの種類
basePath := fmt.Sprintf("/verify/v1/users/%s/stores/%s/subscription-products/%s/transactions/%s", userId, storeId, productId, transactionId) // APIのパス
pathUrl, err := url.Parse(basePath)
if err != nil {
	panic(err)
}

// クエリパラメータの設定
param := url.Values{}
param.Add("appId", gamesAppId)  // GAMESアプリID
param.Add("device", device)     // デバイス(android, ios, win, mac, pc_web, sp_web)
param.Add("userType", userType) // ユーザータイプ(空文字, staff, developer)

pathUrl.RawQuery = param.Encode()
path := pathUrl.String()

response, err := xgApi.SendRequest(method, path, "")
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s (%s) : HTTP status: %v\n", path, device, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res XgVerifyStoreResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		panic(err)
	}

	fmt.Printf("ステータス (%s): %s\n", device, res.Status)

	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
} else {
	// エラー処理
}

XGサブスクリプション通知一覧取得と処理

未処理のXGサブスクリプション通知を取得し、処理します。

「XGサブスクリプション通知一覧取得API」で未処理のXGサブスクリプション通知を取得し、取得したXGサブスクリプション通知を「XGサブスクリプション通知処理API」で処理します。
XGサブスクリプション通知一覧取得APIでは、status=unprocessedとsort=ascを指定して、古い未処理XGサブスクリプション通知から順に取得します。
このサンプルコードでは、取得したXGサブスクリプション通知の最初の1件のみを処理します。

XGサブスクリプション通知一覧取得

「XGサブスクリプション通知一覧取得API」で未処理のXGサブスクリプション通知を取得します。
status=unprocessedとsort=ascを指定して、古い未処理XGサブスクリプション通知から順に取得します。
<?php

// リクエストの準備
$storeId = "appstore";

$method = 'GET'; // HTTPメソッドの種類
$basePath = sprintf("/subscription/v1/users/%s/stores/%s/notifications", $userId, $storeId); // APIのパス
// クエリパラメータを追加
$path = $basePath . "?status=unprocessed&sort=asc"; // 未処理のXGサブスクリプション通知を取得、古いXGサブスクリプション通知から順に取得

try {
    $response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $basePath, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes, true);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        throw new \Exception(sprintf("JSON Decode error: %s", $err));
    }

    if (isset($res['notifications'])) {
        foreach ($res['notifications'] as $notification) {
            printf("transactionId: %s, productId: %s, groupId: %s\n",
                $notification['transactionId'],
                $notification['productId'],
                $notification['groupId']);
        }
    }

    return $res['notifications'] ?? [];
} else {
    // エラー処理
}
// レスポンス用の構造体
type Notification struct {
	TransactionId         string     `json:"transactionId"`
	ProductId             string     `json:"productId"`
	GroupId               string     `json:"groupId"`
	PlanId                string     `json:"planId"`
	Event                 string     `json:"event"`
	SubscriptionStatus    string     `json:"subscriptionStatus"`
	SubscriptionExpiryAt  time.Time  `json:"subscriptionExpiryAt"`
	SubscriptionCancelAt  *time.Time `json:"subscriptionCancelAt"`
	PurchaseTransactionId string     `json:"purchaseTransactionId,omitempty"`
	RenewalTransactionId  string     `json:"renewalTransactionId,omitempty"`
	TransactionAt         time.Time  `json:"transactionAt"`
	EventAt               string     `json:"eventAt"`
	Trigger               string     `json:"trigger"`
	Status                string     `json:"status"`
}

type NotificationsResponse struct {
	Notifications []Notification `json:"notifications"`
}

// リクエストの準備
storeId := "appstore"

method := http.MethodGet                                                                      // HTTPメソッドの種類
basePath := fmt.Sprintf("/subscription/v1/users/%v/stores/%v/notifications", userId, storeId) // APIのパス
// クエリパラメータを追加
u, err := url.Parse(basePath)
if err != nil {
	panic(err)
}
q := u.Query()
q.Set("status", "unprocessed") // 未処理のXGサブスクリプション通知を取得
q.Set("sort", "asc")           // 古いXGサブスクリプション通知から順に取得
u.RawQuery = q.Encode()
path := u.String()

response, err := xgApi.SendRequest(method, path, "")
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", basePath, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res NotificationsResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		log.Printf("JSON Unmarshal error: %v, response: %v", err, string(bodyBytes))
		panic(err)
	}

	for _, notification := range res.Notifications {
		fmt.Printf("transactionId: %s, productId: %s, groupId: %s\n",
			notification.TransactionId,
			notification.ProductId,
			notification.GroupId)
	}
} else {
	// エラー処理
}

XGサブスクリプション通知処理

「XGサブスクリプション通知処理API」でXGサブスクリプション通知を処理します。
XGサブスクリプション通知処理が完了すると、XGサブスクリプション通知が送信され、ゲームサーバーで利用権の付与を行います。
XGサブスクリプション通知のレスポンスで指定したサブスクリプションタグに応じて、ゲーム内通貨が発行されます。
XGサブスクリプション通知の詳細については XGサブスクリプション通知 を参照してください。
<?php

// リクエストの準備
$storeId = "appstore";

$transactionId := "someTransactionId" // XGが発行した取引ID

$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/subscription/v1/users/%s/stores/%s/notifications/transactions/%s/process", $userId, $storeId, $transactionId); // APIのパス
$body = "";

try {
    $response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
    errorResponse($e->getResponse()->getBody());
}

// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
$bodyBytes = $response->getBody();

if ($response->getStatusCode() == 200) {
    $res = json_decode($bodyBytes, true);
    if (is_null($res)) {
        // エラー
        $err = json_last_error_msg();
        error_log(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
        throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $bodyBytes));
    }
    if (isset($res['balance'])) {
        foreach ($res['balance'] as $currencyId => $balance) {
            printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance['free'], $balance['paid']);
        }
    }
    printf("商品ID %s の %s XGサブスクリプション通知の処理が完了しました\n", $res['productId'], $res['event']);
    return $res;
} else {
    // エラー処理
}
// レスポンス用の構造体
type XgBalanceDetail struct {
	Free int64 `json:"free"`
	Paid int64 `json:"paid"`
}

type ProcessNotificationResponse struct {
	TransactionId         string                     `json:"transactionId"`
	TransactionAt         time.Time                  `json:"transactionAt"`
	EventAt               string                     `json:"eventAt"`
	Trigger               string                     `json:"trigger"`
	Quantity              int64                      `json:"quantity"`
	GroupId               string                     `json:"groupId"`
	ProductId             string                     `json:"productId"`
	PlanId                string                     `json:"planId"`
	Event                 string                     `json:"event"`
	SubscriptionStatus    string                     `json:"subscriptionStatus"`
	SubscriptionExpiryAt  time.Time                  `json:"subscriptionExpiryAt"`
	SubscriptionCancelAt  *time.Time                 `json:"subscriptionCancelAt"`
	PurchaseTransactionId string                     `json:"purchaseTransactionId"`
	RenewalTransactionId  string                     `json:"renewalTransactionId"`
	Balance               map[string]XgBalanceDetail `json:"balance"`
	Added                 map[string]XgBalanceDetail `json:"added"`
}

// リクエストの準備
storeId := "appstore"

transactionId := "someTransactionId" // XGが発行した取引ID

method := http.MethodPut                                                                                                                                    // HTTPメソッドの種類
path := fmt.Sprintf("/subscription/v1/users/%v/stores/%v/notifications/transactions/%v/process", userId, storeId, transactionId) // APIのパス
body := ""

response, err := xgApi.SendRequest(method, path, body)
if err != nil {
	panic(err)
}
defer response.Body.Close()

// レスポンスの解析
fmt.Printf("%s : HTTP status: %v\n", path, response.StatusCode)
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
	panic(err)
}

if response.StatusCode == http.StatusOK {
	var res ProcessNotificationResponse
	if err := json.Unmarshal(bodyBytes, &res); err != nil {
		// エラー
		log.Printf("JSON Unmarshal error: %v, response: %v", err, string(bodyBytes))
		panic(err)
	}
	for currencyId, balance := range res.Balance {
		fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
	}
	fmt.Printf("商品ID %s の %s XGサブスクリプション通知の処理が完了しました\n", res.ProductId, res.Event)
} else {
	// エラー処理
}

未処理XGサブスクリプション通知の取得と処理

上記2つのAPIを組み合わせて、未処理のXGサブスクリプション通知を取得し、処理します。
このサンプルコードでは、取得したXGサブスクリプション通知の最初の1件のみを処理します。
各APIの詳細な実装は、上記の「XGサブスクリプション通知一覧取得」と「XGサブスクリプション通知処理」のサンプルコードを参照してください。
<?php

// 1. XGサブスクリプション通知一覧取得APIを呼び出す
// (詳細は「XGサブスクリプション通知一覧取得」のサンプルコードを参照)
$notifications = getNotifications($xgApi, $userId);

if (count($notifications) == 0) {
    printf("未処理のXGサブスクリプション通知はありません\n");
    return;
}

// 最初の1件のXGサブスクリプション通知を取得
$notification = $notifications[0];
$transactionId = $notification['transactionId'];

// 2. XGサブスクリプション通知処理APIを呼び出す
// (詳細は「XGサブスクリプション通知処理」のサンプルコードを参照)
$res = processNotification($xgApi, $userId, $transactionId);

printf("XGサブスクリプション通知処理が完了しました: transactionId=%s, groupId=%s\n", $res['transactionId'], $res['groupId']);
// 1. XGサブスクリプション通知一覧取得APIを呼び出す
// (詳細は「XGサブスクリプション通知一覧取得」のサンプルコードを参照)
notifications, err := getNotifications(xgApi, userId)
if err != nil {
	return err
}

if len(notifications) == 0 {
	fmt.Printf("未処理のXGサブスクリプション通知はありません\n")
	// 処理を終了
}

// 最初の1件のXGサブスクリプション通知を取得
notification := notifications[0]
transactionId := notification.TransactionId

// 2. XGサブスクリプション通知処理APIを呼び出す
// (詳細は「XGサブスクリプション通知処理」のサンプルコードを参照)
res, err := processNotification(xgApi, userId, transactionId)
if err != nil {
	return err
}

fmt.Printf("XGサブスクリプション通知処理が完了しました: transactionId=%s, groupId=%s\n", res.TransactionId, res.GroupId)