APIチュートリアル¶
このチュートリアルでは、XGを利用してユーザーのゲーム内通貨の発行、消費までの一連の流れについて説明します。
なお、この章では環境準備及び認証処理についてはすでに完了しているものとします。
APIへの通信の共通処理の実装¶
ここではPHP言語とGo言語でリクエストの送信とレスポンスの受け取りを行う例を示します。
APIへの通信の共通処理の実装は サンプル を参考にしてください。
XG用のユーザーIDの発行¶
XGを利用するためには、「ゲーム側のユーザーID」に対応する「XG側のユーザーID」の発行が必要です。
以下のコードは、リクエストの送信とレスポンスの受け取りを行う例です。
<?php
// リクエストの送信
$method = 'POST'; // HTTPメソッドの種類
$path = '/user/v1/users'; // APIのパス
$body = json_encode(["gameUserId" => "1234567890"]); // リクエストボディー
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
$userId = "";
if ($response->getStatusCode() == 201) {
$res = json_decode($response->getBody());
if (is_null($res)) {
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s", $err));
}
printf("(XG)UserID: %s\n", $res->id);
// このユーザーIDとゲームサーバー側のユーザーIDを紐付けしてください
$userId = $res->id;
}else{
errorResponse($response->getBody());
}
// リクエスト用の構造体
type Body struct {
GameUserId string `json:"gameUserId"`
}
// レスポンス用の構造体
type PostUserResponse struct {
ID string `json:"id"`
GameUserId string `json:"gameUserId"`
CreatedAt string `json:"createdAt"`
}
// リクエストの送信
method := http.MethodPost // HTTPメソッドの種類
path := "/user/v1/users" // APIのパス
requestBodyBytes, err := json.Marshal(Body{
GameUserId: "1234567890",
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusCreated {
var res PostUserResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
log.Fatalf("JSON Unmarshal error: %v", err)
} else {
fmt.Printf("(XG)UserID: %s\n", res.ID)
// このユーザーIDとゲームサーバー側のユーザーIDを紐付けしてください
userId = res.ID
}
return userId, err
} else {
// エラー処理
}
実行時の出力結果のイメージは以下のようになります。
(XG)UserID: 8ef80b0a-ef80-4105-a978-3943f51bd14c
この手順で取得したユーザーIDと、「ゲーム側のユーザーID」を紐付けして保存してください。
これ以降、ゲーム内通貨の操作をする際にはこのユーザーIDを利用してリクエストしてください。
無償通貨の発行¶
次に「ログインボーナス」として無償通貨「gem」を1000個発行します。
ゲーム内通貨を操作するリクエストをする際には、ゲームサーバー側で取引ID(transactionID)の発行が必要です。
<?php
// リクエストの準備
$storeId = "googleplay";
$transactionId = Uuid::uuid4()->toString(); // 取引IDを作成します。(UUIDを利用)
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/currency/v1/users/%s/stores/%s/issue/free", $userId, $storeId); // APIのパス
$body = json_encode([
'transactions' => [
[
'transactionId' => $transactionId,
'description' => 'ログインボーナス',
'currency' => [
'gem' => [
'quantity' => 1000,
],
],
],
],
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
// responseBody(JSON)を構造体に変換
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
// 現在のゲーム内通貨の残高を表示
if (isset($res->balance)) {
foreach ($res->balance as $currencyId => $balance) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
}
}
} else {
// エラー処理
}
// リクエスト用の構造体
type XgCurrencyDetail struct {
Quantity int64 `json:"quantity"`
ExpiryAt *time.Time `json:"expiryAt,omitempty"`
}
type XgTransactionRequestDetail struct {
TransactionId string `json:"transactionId"`
Description string `json:"description"`
Currency map[string]XgCurrencyDetail `json:"currency"`
}
type Body struct {
Transactions []XgTransactionRequestDetail `json:"transactions"`
}
// レスポンス用の構造体
type XgTransactionResponseDetail struct {
TransactionId string `json:"transactionId"`
TransactionAt time.Time `json:"transactionAt"`
Status string `json:"status"`
Description string `json:"description"`
Currency map[string]XgCurrencyDetail `json:"currency"`
}
type XgBalanceDetail struct {
Free int64 `json:"free"`
Paid int64 `json:"paid"`
}
type XgIsueeFreeResponse struct {
Status string `json:"status"`
Transactions []XgTransactionResponseDetail `json:"transactions"`
Balance map[string]XgBalanceDetail `json:"balance"`
}
// リクエストの準備
storeId := "googleplay"
transactionId, err := uuid.NewV4() // 取引IDを作成します。(UUIDを利用)
if err != nil {
return err
}
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/currency/v1/users/%s/stores/%s/issue/free", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
requestBodyBytes, err := json.Marshal(Body{
Transactions: []XgTransactionRequestDetail{
{
TransactionId: transactionId.String(),
Description: "ログインボーナス",
Currency: map[string]XgCurrencyDetail{
"gem": {
Quantity: 1000,
},
},
},
},
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusOK {
// responseBody(JSON)を構造体に変換
var res XgIsueeFreeResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
return err
}
// 現在のゲーム内通貨の残高を表示
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
} else {
// エラー処理
}
ゲーム内通貨(無償通貨のみ)の消費¶
次にゲーム内で無償通貨「gem」を500消費して、ガチャチケットを1枚獲得する例を説明します。
ゲーム内通貨を操作するリクエストをする際には、ゲームサーバー側で取引ID(transactionID)の発行が必要です。
ゲーム内通貨の消費状態と、ゲーム内でのアイテム発行状態を管理するレコードを作成してください。(ここでは「取引状態管理テーブル」と表現します。)
このテーブルでは、XG側のレスポンスと、ゲーム側のアイテム付与状態に応じて状態を管理するようにします。
これは処理が中断した際の再開処理をする際に必要となります。
REQUEST BODYのパラメーター「currencyType」に「free」を指定して消費リクエストを呼び出します。(なお、「currencyType」を指定しない場合は、無償通貨もしくは有償通貨は予め登録してあるゲーム内通貨の消費順に従って消費します。)
XGから成功レスポンスが戻ってきましたので「取引状態管理テーブル」の状態を「ゲーム内通貨消費済み」に更新します。
ゲーム内アイテム「ガチャチケット」を1枚発行します。
アイテムの付与が完了しましたら、「取引状態管理テーブル」の状態を「アイテム発行済み」に更新します。 これで一連のゲーム内通貨の消費とアイテムの発行処理が完了となります。
<?php
//-----------------------------------------------------------
// ゲーム内通貨の消費状態とアイテム付与の状態管理のための実装
//-----------------------------------------------------------
class TransactionConsumeStatus{
public function setConsumeCurrencyStatus($transactionId, $userId, $status) {
// データベース更新処理などを記入
}
}
//-----------------------------------------------------------
// 無償通貨500gem消費し、ガチャチケット1枚獲得する実装イメージ
//-----------------------------------------------------------
// ゲーム内通貨の消費とアイテム付与の状態管理するクラス
$transactionStatus = new TransactionConsumeStatus();
// リクエストの準備
$storeId = "googleplay";
$transactionId = Uuid::uuid4()->toString();
// リカバリー対策として、取引状態及びアイテム等の付与状態を保存する
$transactionStatus->setConsumeCurrencyStatus($transactionId, $userId, "処理前");
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/currency/v1/users/%s/stores/%s/consume", $userId, $storeId); // APIのパス
// 無償通貨500gem消費して、ガチャチケット1枚獲得
$body = json_encode([
'transactionId' => $transactionId,
'description' => 'ガチャチケット',
'quantity' => 1,
'currencyType' => "free",
'transaction' => [
"gem" => 500,
],
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
//エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
// ゲーム内通貨の残高
if (isset($res->balance)) {
foreach ($res->balance as $currencyId => $balance) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
}
}
// XG側でゲーム内通貨を消費済み状態に変更
$transactionStatus->setConsumeCurrencyStatus($transactionId, $userId, "ゲーム内通貨消費済み");
/*
ガチャチケットを1枚獲得
*/
// アイテム付与完了状態にする
$transactionStatus->setConsumeCurrencyStatus($transactionId, $userId, "アイテム付与済み");
} else {
// エラー処理
}
//-----------------------------------------------------------
// ゲーム内通貨の消費状態とアイテム付与の状態管理のための実装
//-----------------------------------------------------------
type TransactionConsumeStatus struct{}
func (t *TransactionConsumeStatus) setConsumeCurrencyStatus(transactionId any, userId any, status any) error {
// データベース更新処理などを記入
return nil
}
//-----------------------------------------------------------
// 無償通貨500gem消費し、ガチャチケット1枚獲得する実装イメージ
//-----------------------------------------------------------
// リクエスト用の構造体
type Body struct {
TransactionId string `json:"transactionId"`
Description string `json:"description"`
Quantity int64 `json:"quantity"`
CurrencyType string `json:"currencyType"`
Transaction map[string]int64 `json:"transaction"`
}
// ゲーム内通貨の消費とアイテム付与の状態管理する構造体
transactionStatus := TransactionConsumeStatus{}
// レスポンス用の構造体
type XgBalanceDetail struct {
Free int64 `json:"free"`
Paid int64 `json:"paid"`
}
type XgConsumeResponse struct {
// TransactionId 取引ID
TransactionId string `json:"transactionId"`
// TransactionAt ゲーム内通貨消費日時(UTC)
TransactionAt time.Time `json:"transactionAt"`
// Status 結果ステータス
Status string `json:"status"`
// StoreId ストアID
StoreId string `json:"storeId"`
// Balance ゲーム内通貨の残高
Balance map[string]XgBalanceDetail `json:"balance"`
}
// リクエストの準備
storeId := "googleplay"
transactionId, err := uuid.NewV4()
if err != nil {
panic(err)
}
// リカバリー対策として、取引状態及びアイテム等の付与状態を保存する
err = transactionStatus.setConsumeCurrencyStatus(transactionId, userId, "処理前")
if err != nil {
// エラー処理
panic(err)
}
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/currency/v1/users/%v/stores/%v/consume", userId, storeId) // APIのパス
// 無償通貨500gem消費して、ガチャチケット1枚獲得
requestBodyBytes, err := json.Marshal(Body{
TransactionId: transactionId.String(),
Description: "ガチャチケット",
Quantity: 1,
CurrencyType: "free",
Transaction: map[string]int64{
"gem": 500,
},
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusOk {
var res XgConsumeResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
// ゲーム内通貨の残高
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
// XG側でゲーム内通貨を消費済み状態に変更
err = transactionStatus.setConsumeCurrencyStatus(transactionId, userId, "ゲーム内通貨消費済み")
if err != nil {
// エラー処理
panic(err)
}
/*
ガチャチケットを1枚獲得
*/
// アイテム付与完了状態にする
err = transactionStatus.setConsumeCurrencyStatus(transactionId, userId, "アイテム付与済み")
if err != nil {
// エラー処理
panic(err)
}
} else {
// エラー処理
}
課金アイテム購入に伴うゲーム内通貨の発行¶
App Store¶
<?php
// リクエストの準備
$storeId = "appstore";
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/purchase/v1/users/%s/stores/%s", $userId, $storeId); // APIのパス
$body = json_encode([
'transactionId' => 'someTransactionId', // ストアから発行されたトランザクションID
'productId' => 'someProductId', // 商品ID
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
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"
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/purchase/v1/users/%v/stores/%v", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
TransactionId: "someTransactionId", // ストアから発行されたトランザクションID
ProductId: "someProductId", // 商品ID
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
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 {
// エラー
panic(err)
}
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
} else {
// エラー処理
}
DMM GAMES¶
<?php
// リクエストの準備
$storeId = "dmmgames";
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/purchase/v1/users/%s/stores/%s", $userId, $storeId); // APIのパス
$body = json_encode([
'transactionId' => $transactionId, // 購入トークン
'productId' => $productId, // 商品ID
'games' => [
"appId" => $gamesAppId, // GAMESアプリ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());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
if (isset($res->balance)) {
foreach ($res->balance as $currencyId => $balance) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
}
}
} else {
// エラー処理
}
// リクエスト用の構造体
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"`
Balance map[string]XgBalanceDetail `json:"balance"`
Added map[string]XgBalanceDetail `json:"added"`
}
// リクエストの準備
storeId := "dmmgames"
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/purchase/v1/users/%v/stores/%v", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
TransactionId: "someToken", // 購入トークン
ProductId: "someProductId", // 商品ID
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()
// レスポンスの解析
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 {
// エラー
panic(err)
}
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
} else {
// エラー処理
}
Google Play¶
<?php
// リクエストの準備
$storeId = "googleplay";
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/purchase/v1/users/%s/stores/%s", $userId, $storeId); // APIのパス
$body = json_encode([
'transactionId' => 'someToken', // 購入トークン
'productId' => 'someProductId', // 商品ID
'productType' => 'someProductType', // 商品タイプ
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
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"`
ProductType string `json:"productType"`
}
// レスポンス用の構造体
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 := "googleplay"
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/purchase/v1/users/%v/stores/%v", userId, storeId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
TransactionId: "someToken", // 購入トークン
ProductId: "someProductId", // 商品ID
ProductType: "someProductType", // 商品タイプ
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
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 {
// エラー
panic(err)
}
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
} else {
// エラー処理
}
DMMチャージセンター¶
注釈
DMMチャージセンターを利用する際は前提条件があります。DMMチャージセンターについて を参照してください。
<?php
// リクエストの準備
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/purchase/v1/users/%s/stores/chargecenter/dmmgames", $userId); // APIのパス
$body = json_encode([
'purchaseId' => 'somePurchaseId', // 購入ID
'productId' => 'someProductId', // 商品ID
'storeId' => $storeId, // ゲーム内通貨を発行するストアID(googleplay, appstore)
'gameUserId' => $gameUserId, // ゲームアプリ内のユーザーID
'purchaseAt' => $purcahseAt->format(DateTime::RFC3339), // 購入日時
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
printf("%s : HTTP status: %d\n", $path, $response->getStatusCode());
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
if (isset($res->balance)) {
foreach ($res->balance as $currencyId => $balance) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
}
}
} else {
// エラー処理
}
// リクエスト用の構造体
type Body struct {
ProductId string `json:"productId"`
PurchaseId string `json:"purchaseId"`
StoreId string `json:"storeId"`
GameUserId string `json:"gameUserId"`
PurchaseAt time.Time `json:"purchaseAt"`
}
// レスポンス用の構造体
type XgBalanceDetail struct {
Free int64 `json:"free"`
Paid int64 `json:"paid"`
}
type XgPurchaseStoreChargecenterResponse struct {
PurchaseId string `json:"purchaseId"`
PurchaseAt time.Time `json:"purchaseAt"`
Quantity int64 `json:"quantity"`
Balance map[string]XgBalanceDetail `json:"balance"`
Added map[string]XgBalanceDetail `json:"added"`
}
// リクエストの準備
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/purchase/v1/users/%v/stores/chargecenter/dmmgames", userId) // APIのパス
requestBodyBytes, err := json.Marshal(Body{
PurchaseId: "somePurchaseId", // 購入ID
ProductId: "someProductId", // 商品ID
StoreId: storeId, // ゲーム内通貨を発行するストアID(googleplay, appstore)
GameUserId: gameUserId, // ゲームアプリ内のユーザーID
PurchaseAt: purcahseAt.UTC().Truncate(time.Second), // 購入日時(UTC)
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusOK {
var res XgPurchaseStoreChargecenterResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
} else {
// エラー処理
}
課金アイテム購入の検証¶
App Store¶
<?php
// リクエストの準備
$storeId = "appstore";
$productId = 'someProductId'; // 商品ID
$transactionId = 'someToken'; // ストアから発行されたトランザクションID
$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/verify/v1/users/%s/stores/%s/products/%s/transactions/%s", $userId, $storeId, $productId, $transactionId); // APIのパス
try {
$response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
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/products/%s/transactions/%s", userId, storeId, productId, transactionId) // APIのパス
response, err := xgApi.SendRequest(method, path, "")
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
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¶
<?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/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());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
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 {
// エラー処理
errorResponse($response->getBody());
}
// レスポンス用の構造体
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/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()
// レスポンスの解析
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¶
<?php
// リクエストの準備
$storeId = "googleplay";
$productId = 'someProductId'; // 商品ID
$transactionId = 'someToken'; // 購入トークン
$param = [];
$param["productType"] = $productType; // 商品タイプ (consumable, non-consumable)
$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/verify/v1/users/%s/stores/%s/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());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
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/products/%s/transactions/%s", userId, storeId, productId, transactionId) // APIのパス
pathUrl, err := url.Parse(basePath)
if err != nil {
panic(err)
}
// クエリパラメータの設定
param := url.Values{}
param.Add("productType", productType) // 商品タイプ (consumable, non-consumable)
pathUrl.RawQuery = param.Encode()
path := pathUrl.String()
response, err := xgApi.SendRequest(method, path, "")
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
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 {
// エラー処理
}
ゲーム内通貨(有償通貨のみ)の消費¶
最後にゲーム内で有償通貨「gem」を100消費して、プレミアガチャチケットを1枚獲得する例を説明します。
ゲーム内通貨を操作するリクエストをする際には、ゲームサーバー側で取引ID(transactionID)の発行が必要です。
ゲーム内通貨の消費状態と、ゲーム内でのアイテム発行状態を管理するレコードを作成してください。(ここでは「取引状態管理テーブル」と表現します。)
このテーブルでは、XG側のレスポンスと、ゲーム側のアイテム付与状態に応じて状態を管理するようにします。
これは処理が中断した際の再開処理をする際に必要となります。
REQUEST BODYのパラメーター「currencyType」に「paid」を指定して消費リクエストを呼び出します。(なお、「currencyType」を指定しない場合は、無償通貨もしくは有償通貨は予め登録してあるゲーム内通貨の消費順に従って消費します。)
XGから成功レスポンスが戻ってきましたので「取引状態管理テーブル」の状態を「ゲーム内通貨消費済み」に更新します。
ゲーム内アイテム「プレミアガチャチケット」を1枚発行します。
アイテムの付与が完了しましたら、「取引状態管理テーブル」の状態を「アイテム付与済み」に更新します。 これで一連のゲーム内通貨の消費とアイテムの発行処理が完了となります。
<?php
//-----------------------------------------------------------
// ゲーム内通貨の消費状態とアイテム付与の状態管理のための実装
//-----------------------------------------------------------
class TransactionConsumeStatus{
public function setConsumeCurrencyStatus($transactionId, $userId, $status) {
// データベース更新処理などを記入
}
}
//-----------------------------------------------------------
// 有償通貨100gem消費し、プレミアガチャチケット1枚獲得する実装イメージ
//-----------------------------------------------------------
// ゲーム内通貨の消費とアイテム付与の状態管理するクラス
$transactionStatus = new TransactionConsumeStatus();
// リクエストの準備
$storeId = "googleplay";
$transactionId = Uuid::uuid4()->toString();
// リカバリー対策として、取引状態及びアイテム等の付与状態を保存する
$transactionStatus->setConsumeCurrencyStatus($transactionId, $userId, "処理前");
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/currency/v1/users/%s/stores/%s/consume", $userId, $storeId); // APIのパス
// 有償通貨100gem消費して、プレミアガチャチケット1枚獲得
$body = json_encode([
'transactionId' => $transactionId,
'description' => 'プレミアガチャチケット',
'quantity' => 1,
'currencyType' => "paid",
'transaction' => [
"gem" => 100,
],
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
//エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
// ゲーム内通貨の残高
if (isset($res->balance)) {
foreach ($res->balance as $currencyId => $balance) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
}
}
// XG側でゲーム内通貨を消費済み状態に変更
$transactionStatus->setConsumeCurrencyStatus($transactionId, $userId, "ゲーム内通貨消費済み");
/*
プレミアガチャチケットを1枚獲得
*/
// アイテム付与完了状態にする
$transactionStatus->setConsumeCurrencyStatus($transactionId, $userId, "アイテム付与済み");
} else {
// エラー処理
}
//-----------------------------------------------------------
// ゲーム内通貨の消費状態とアイテム付与の状態管理のための実装
//-----------------------------------------------------------
type TransactionConsumeStatus struct{}
func (t *TransactionConsumeStatus) setConsumeCurrencyStatus(transactionId any, userId any, status any) error {
// データベース更新処理などを記入
return nil
}
//-----------------------------------------------------------
// 有償通貨100gem消費し、プレミアガチャチケット1枚獲得する実装イメージ
//-----------------------------------------------------------
// リクエスト用の構造体
type Body struct {
TransactionId string `json:"transactionId"`
Description string `json:"description"`
Quantity int64 `json:"quantity"`
CurrencyType string `json:"currencyType"`
Transaction map[string]int64 `json:"transaction"`
}
// ゲーム内通貨の消費とアイテム付与の状態管理する構造体
transactionStatus := TransactionConsumeStatus{}
// レスポンス用の構造体
type XgBalanceDetail struct {
Free int64 `json:"free"`
Paid int64 `json:"paid"`
}
type XgConsumeResponse struct {
// TransactionId 取引ID
TransactionId string `json:"transactionId"`
// TransactionAt ゲーム内通貨消費日時(UTC)
TransactionAt time.Time `json:"transactionAt"`
// Status 結果ステータス
Status string `json:"status"`
// StoreId ストアID
StoreId string `json:"storeId"`
// Balance ゲーム内通貨の残高
Balance map[string]XgBalanceDetail `json:"balance"`
}
// リクエストの準備
storeId := "googleplay"
transactionId, err := uuid.NewV4()
if err != nil {
panic(err)
}
// リカバリー対策として、取引状態及びアイテム等の付与状態を保存する
err = transactionStatus.setConsumeCurrencyStatus(transactionId, userId, "処理前")
if err != nil {
// エラー処理
panic(err)
}
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/currency/v1/users/%v/stores/%v/consume", userId, storeId) // APIのパス
// 有償通貨100gem消費して、プレミアガチャチケット1枚獲得
requestBodyBytes, err := json.Marshal(Body{
TransactionId: transactionId.String(),
Description: "プレミアガチャチケット",
Quantity: 1,
CurrencyType: "paid",
Transaction: map[string]int64{
"gem": 100,
},
})
if err != nil {
panic(err)
}
body := string(requestBodyBytes)
response, err := xgApi.SendRequest(method, path, body)
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusOk {
var res XgConsumeResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
// ゲーム内通貨の残高
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
// XG側でゲーム内通貨を消費済み状態に変更
err = transactionStatus.setConsumeCurrencyStatus(transactionId, userId, "ゲーム内通貨消費済み")
if err != nil {
// エラー処理
panic(err)
}
/*
プレミアガチャチケットを1枚獲得
*/
// アイテム付与完了状態にする
err = transactionStatus.setConsumeCurrencyStatus(transactionId, userId, "アイテム付与済み")
if err != nil {
// エラー処理
panic(err)
}
} else {
// エラー処理
}
ゲーム内通貨消費の取消し¶
リカバリーでゲーム内アイテムの付与が適さないアイテムの場合、ゲーム内通貨の消費のリカバリーにてゲーム内通貨消費の取消しを行います。
注釈
リカバリーでゲーム内アイテムの付与が適さないアイテムは以下を想定しています。
コンティニューやスタミナ回復等、ユーザーがリクエストした時に効果が発揮されないと価値提供ができないアイテム
期間限定品のように定められた期間を超えて付与されても使用できない(ガチャチケット)、あるいは都合の悪いアイテム
<?php
// ゲーム内通貨の消費とアイテム付与の状態管理するクラス
$transactionStatus = new TransactionConsumeStatus();
// リクエストの準備
$storeId = "googleplay";
$method = 'PUT'; // HTTPメソッドの種類
$path = sprintf("/currency/v1/users/%s/stores/%s/consume/cancel", $userId, $storeId); // APIのパス
// ゲーム内通貨消費の取消し
$body = json_encode([
'transactionId' => $transactionId, // ゲーム内通貨の消費で指定した取引ID
'description' => 'アイテムの使用期限切れによりアイテム付与が出来ないため', // 取消し理由
]);
try {
$response = $xgApi->request($method, $path, $body);
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
//エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
// ゲーム内通貨の残高
if (isset($res->balance)) {
print("ゲーム内通貨の残高\n");
foreach ($res->balance as $currencyId => $balance) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $balance->free, $balance->paid);
}
}
// 発行されたゲーム内通貨の数量
if(isset($res->added)){
print("発行されたゲーム内通貨の数量\n");
foreach ($res->added as $currencyId => $added) {
printf("%s 無償: %d, 有償: %d\n", $currencyId, $added->free, $added->paid);
}
}
// ゲーム内通貨の消費とアイテム付与の状態レコードを削除
$transactionStatus->deleteConsumeCurrencyStatus($transactionId, $userId);
} else {
// エラー処理
}
// リクエスト用の構造体
type Body struct {
TransactionId string `json:"transactionId"`
Description string `json:"description"`
}
// レスポンス用の構造体
type XgCurrencyDetail struct {
Free int64 `json:"free"`
Paid int64 `json:"paid"`
}
type XgConsumeResponse struct {
// TransactionId 取引ID
TransactionId string `json:"transactionId"`
// TransactionAt ゲーム内通貨消費の取消日時(UTC)
TransactionAt time.Time `json:"transactionAt"`
// Status 結果ステータス
Status string `json:"status"`
// Balance ゲーム内通貨の残高
Balance map[string]XgCurrencyDetail `json:"balance"`
// Added 発行されたゲーム内通貨の数量
Added map[string]XgCurrencyDetail `json:"added"`
}
// ゲーム内通貨の消費とアイテム付与の状態管理する構造体
transactionStatus := TransactionConsumeStatus{}
// リクエストの準備
storeId := "googleplay"
method := http.MethodPut // HTTPメソッドの種類
path := fmt.Sprintf("/currency/v1/users/%v/stores/%v/consume/cancel", userId, storeId) // APIのパス
// ゲーム内通貨消費の取消し
requestBodyBytes, err := json.Marshal(Body{
TransactionId: transactionId, // ゲーム内通貨の消費で指定した取引ID
Description: "アイテムの使用期限切れによりアイテム付与が出来ないため", // 取消し理由
})
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 XgConsumeResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
// ゲーム内通貨の残高
fmt.Println("ゲーム内通貨の残高")
for currencyId, balance := range res.Balance {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, balance.Free, balance.Paid)
}
// 発行されたゲーム内通貨の数量
fmt.Println("発行されたゲーム内通貨の数量")
for currencyId, added := range res.Added {
fmt.Printf("%s 無償: %d, 有償: %d\n", currencyId, added.Free, added.Paid)
}
// ゲーム内通貨の消費とアイテム付与の状態レコードを削除
err = transactionStatus.deleteConsumeCurrencyStatus(transactionId, userId)
if err != nil {
// エラー処理
painc(err)
}
} else {
// エラー処理
}
履歴の取得¶
ゲーム内商品の購入履歴の取得¶
各ストアやゲーム内通貨を使用して購入した商品を取得します。
下記の各履歴を最大各100件まで取得できます。
各ストアでゲーム内商品(ゲーム内通貨の購入や直課金など)を購入した履歴("store")
ゲーム内通貨を消費してゲーム内商品(アイテムや権利行使など)を購入した履歴("ingame")
<?php
// リクエストの準備
$param = [];
if (!empty($purchasedWith)) {
$param["purchasedWith"] = implode(",", $purchasedWith);
}
if (!is_null($startAt)) {
$startAt->setTimezone(new DateTimeZone("UTC"));
$param["startAt"] = $startAt->format(DateTime::RFC3339);
}
if (!is_null($endAt)) {
$endAt->setTimezone(new DateTimeZone("UTC"));
$param["endAt"] = $endAt->format(DateTime::RFC3339);
}
if (!is_null($limit)) {
$param["limit"] = $limit;
}
if (!is_null($pageNumber)) {
$param["pageNumber"] = $pageNumber;
}
if (!is_null($transactionId)) {
$param["transactionId"] = $transactionId;
}
$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/history/v1/users/%s/products?%s", $userId, http_build_query($param)); // APIのパス
try {
$response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
printf("各ストアの履歴\n");
foreach($res->store as $store) {
printf("%s %s(%s) %d個 価格:%d\n", $store->storeId, $store->productName, $store->productId, $store->quantity, $store->price);
}
printf("ゲーム内通貨の履歴\n");
foreach($res->ingame as $ingame) {
$cur = json_encode($ingame->currency);
printf("%s %s %d個 消費したゲーム内通貨:%s\n", $ingame->storeId, $ingame->description, $ingame->quantity, $cur);
}
} else {
// エラー処理
}
// レスポンス用の構造体
type Store struct {
StoreId string `json:"storeId"`
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Quantity int64 `json:"quantity"`
Price int64 `json:"price"`
StoreTransactionId string `json:"storeTransactionId"`
TransactionAt time.Time `json:"transactionAt"`
}
type IngameCurrencyDetail struct {
Total int64 `json:"total"`
Paid int64 `json:"paid"`
Free int64 `json:"free"`
}
type Ingame struct {
StoreId string `json:"storeId"`
Description string `json:"description"`
Quantity int64 `json:"quantity"`
Currency map[string]IngameCurrencyDetail `json:"currency"`
TransactionId string `json:"transactionId"`
TransactionAt time.Time `json:"transactionAt"`
}
type XgHistoryProductsResponse struct {
Store []Store
Ingame []Ingame
}
// リクエストの準備
method := http.MethodGet // HTTPメソッドの種類
basePath := fmt.Sprintf("/history/v1/users/%s/products", userId) // APIのパス
pathUrl, err := url.Parse(basePath)
if err != nil {
panic(err)
}
param := url.Values{}
if len(purchasedWith) > 0 {
param.Add("purchasedWith", strings.Join(purchasedWith, ","))
}
if startAt != nil {
param.Add("startAt", startAt.UTC().Format(time.RFC3339))
}
if endAt != nil {
param.Add("startAt", startAt.UTC().Format(time.RFC3339))
}
if limit != nil {
param.Add("limit", fmt.Sprintf("%d", *limit))
}
if pageNumber != nil {
param.Add("pageNumber", fmt.Sprintf("%d", *pageNumber))
}
if transactionId != nil {
param.Add("transactionId", *transactionId)
}
pathUrl.RawQuery = param.Encode()
path := pathUrl.String()
response, err := xgApi.SendRequest(method, path, "")
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusOK {
var res XgHistoryProductsResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
fmt.Println("各ストアの履歴")
for _, store := range res.Store {
fmt.Printf("%s %s(%s) %d個 価格:%d\n", store.StoreId, store.ProductName, store.ProductId, store.Quantity, store.Price)
}
fmt.Println("ゲーム内通貨の履歴")
for _, ingame := range res.Ingame {
cur, err := json.Marshal(ingame.Currency)
if err != nil {
fmt.Printf("%v\n", err)
}
fmt.Printf("%s %s %d個 消費したゲーム内通貨:%s\n", ingame.StoreId, ingame.Description, ingame.Quantity, string(cur))
}
} else {
// エラー処理
}
履歴(V2)の取得¶
課金アイテムの購入履歴の取得¶
課金アイテムの購入履歴を取得します。
注釈
「transactionId」がある場合
「startAt」と「endAt」の値は無視されます。
「storeId」が必須になります。
<?php
// リクエストの準備
$param = [];
if (!is_null($storeId)) {
$param["storeId"] = implode(",", $storeId);
}
if (!is_null($startAt)) {
$startAt->setTimezone(new DateTimeZone("UTC"));
$param["startAt"] = $startAt->format(DateTime::RFC3339);
}
if (!is_null($endAt)) {
$endAt->setTimezone(new DateTimeZone("UTC"));
$param["endAt"] = $endAt->format(DateTime::RFC3339);
}
if (!is_null($limit)) {
$param["limit"] = $limit;
}
if (!is_null($pageNumber)) {
$param["pageNumber"] = $pageNumber;
}
if (!is_null($transactionId)) {
$param["transactionId"] = $transactionId;
}
if (!is_null($sortAsc)) {
$param["sort"] = $sortAsc ? "asc": "desc";
}
if (!is_null($timeZone)) {
$param["timeZone"] = $timeZone;
}
$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/history/v2/users/%s/purchases?%s", $userId, http_build_query($param)); // APIのパス
try {
$response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
// レスポンスの解析
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
printf("購入履歴\n");
foreach($res->purchases as $row) {
printf("%s %s %s(%s) %d個 価格:%d\n", $row->transactionAt, $row->storeId, $row->productName, $row->productId, $row->quantity, $row->price);
}
} else {
// エラー処理
}
// レスポンス用の構造体
type purchase struct {
TransactionAt time.Time `json:"transactionAt"`
TransactionId string `json:"transactionId"`
StoreId string `json:"storeId"`
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Quantity int64 `json:"quantity"`
Price int64 `json:"price"`
}
type XgHistoryV2PurchasesResponse struct {
TotalCount int64 `json:"totalCount"`
Purchases []purchase `json:"purchases"`
}
// リクエストの準備
method := http.MethodGet // HTTPメソッドの種類
basePath := fmt.Sprintf("/history/v2/users/%s/purchases", userId) // APIのパス
pathUrl, err := url.Parse(basePath)
if err != nil {
return err
}
param := url.Values{}
if len(storeId) > 0 {
param.Add("storeId", strings.Join(storeId, ","))
}
if startAt != nil {
param.Add("startAt", startAt.UTC().Format(time.RFC3339))
}
if endAt != nil {
param.Add("startAt", startAt.UTC().Format(time.RFC3339))
}
if limit != nil {
param.Add("limit", fmt.Sprintf("%d", *limit))
}
if pageNumber != nil {
param.Add("pageNumber", fmt.Sprintf("%d", *pageNumber))
}
if transactionId != nil {
param.Add("transactionId", *transactionId)
}
if sortAsc != nil {
if *sortAsc {
param.Add("sort", "asc")
} else {
param.Add("sort", "desc")
}
}
if timeZone != nil {
param.Add("timeZone", *timeZone)
}
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 XgHistoryV2PurchasesResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
fmt.Println("購入履歴")
for _, row := range res.Purchases {
fmt.Printf("%s %s %s(%s) %d個 価格:%d\n", row.TransactionAt.String(), row.StoreId, row.ProductName, row.ProductId, row.Quantity, row.Price)
}
} else {
// エラー処理
}
ゲーム内通貨の取引履歴の取得¶
ゲーム内通貨の取引履歴を取得します。
注釈
「transactionId」がある場合
「startAt」と「endAt」の値は無視されます。
「storeId」が必須になります。
<?php
// リクエストの準備
$param = [];
if (!is_null($storeId)) {
$param["storeId"] = implode(",", $storeId);
}
if (!is_null($startAt)) {
$startAt->setTimezone(new DateTimeZone("UTC"));
$param["startAt"] = $startAt->format(DateTime::RFC3339);
}
if (!is_null($endAt)) {
$endAt->setTimezone(new DateTimeZone("UTC"));
$param["endAt"] = $endAt->format(DateTime::RFC3339);
}
if (!is_null($limit)) {
$param["limit"] = $limit;
}
if (!is_null($pageNumber)) {
$param["pageNumber"] = $pageNumber;
}
if (!is_null($transactionId)) {
$param["transactionId"] = $transactionId;
}
if (!is_null($transactionType)) {
$param["transactionType"] = implode(",", $transactionType);
}
if (!is_null($currencyId)) {
$param["currencyId"] = implode(",", $currencyId);
}
if (!is_null($currencyType)) {
$param["currencyType"] = $currencyType;
}
if (!is_null($sortAsc)) {
$param["sort"] = $sortAsc ? "asc": "desc";
}
if (!is_null($timeZone)) {
$param["timeZone"] = $timeZone;
}
$method = 'GET'; // HTTPメソッドの種類
$path = sprintf("/history/v2/users/%s/currency-transactions?%s", $userId, http_build_query($param)); // APIのパス
try {
$response = $xgApi->request($method, $path, "");
} catch (BadResponseException $e) {
errorResponse($e->getResponse()->getBody());
}
if ($response->getStatusCode() == 200) {
$res = json_decode($response->getBody());
if (is_null($res)) {
// エラー
$err = json_last_error_msg();
throw new \Exception(sprintf("JSON Decode error: %s, response: %s", $err, $response->getBody()));
}
printf("取引履歴\n");
foreach($res->currencyTransactions as $row) {
$balance = "";
if ($row->balance != null) {
$balance = sprintf("残高=%d", $row->balance);
}
printf("%s %s %s %s(%s)=%d %s\n", $row->transactionAt, $row->storeId, $row->transactionType, $row->currencyId, $row->currencyType, $row->quantity, $balance);
}
} else {
// エラー処理
}
// レスポンス用の構造体
type currencyTransaction struct {
TransactionAt time.Time `json:"transactionAt"`
TransactionId string `json:"transactionId"`
TransactionType string `json:"transactionType"`
StoreId string `json:"storeId"`
Description string `json:"description"`
CurrencyId string `json:"currencyId"`
CurrencyType string `json:"currencyType"`
Quantity int64 `json:"quantity"`
Balance *int64 `json:"balance,omitempty"`
}
type XgHistoryV2CurrencyTransactionsResponse struct {
TotalCount int64 `json:"totalCount"`
CurrencyTransactions []currencyTransaction `json:"currencyTransactions"`
}
// リクエストの準備
method := http.MethodGet // HTTPメソッドの種類
basePath := fmt.Sprintf("/history/v2/users/%s/currency-transactions", userId) // APIのパス
pathUrl, err := url.Parse(basePath)
if err != nil {
return err
}
param := url.Values{}
if len(storeId) > 0 {
param.Add("storeId", strings.Join(storeId, ","))
}
if startAt != nil {
param.Add("startAt", startAt.UTC().Format(time.RFC3339))
}
if endAt != nil {
param.Add("startAt", startAt.UTC().Format(time.RFC3339))
}
if limit != nil {
param.Add("limit", fmt.Sprintf("%d", *limit))
}
if pageNumber != nil {
param.Add("pageNumber", fmt.Sprintf("%d", *pageNumber))
}
if transactionId != nil {
param.Add("transactionId", *transactionId)
}
if transactionType != nil && len(*transactionType) > 0 {
param.Add("transactionType", strings.Join(*transactionType, ","))
}
if currencyId != nil && len(*currencyId) > 0 {
param.Add("currencyId", strings.Join(*currencyId, ","))
}
if currencyType != nil {
param.Add("currencyType", *currencyType)
}
if sortAsc != nil {
if *sortAsc {
param.Add("sort", "asc")
} else {
param.Add("sort", "desc")
}
}
if timeZone != nil {
param.Add("timeZone", *timeZone)
}
pathUrl.RawQuery = param.Encode()
path := pathUrl.String()
response, err := xgApi.SendRequest(method, path, "")
if err != nil {
panic(err)
}
defer response.Body.Close()
// レスポンスの解析
bodyBytes, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}
if response.StatusCode == http.StatusOK {
var res XgHistoryV2CurrencyTransactionsResponse
if err := json.Unmarshal(bodyBytes, &res); err != nil {
// エラー
panic(err)
}
fmt.Println("取引履歴")
for _, row := range res.CurrencyTransactions {
balance := ""
if row.Balance != nil {
balance = fmt.Sprintf("残高=%d", *row.Balance)
}
fmt.Printf("%s %s %s %s(%s)=%d %s\n", row.TransactionAt.String(), row.StoreId, row.TransactionType, row.CurrencyId, row.CurrencyType, row.Quantity, balance)
}
} else {
// エラー処理
}