AppStoreConnect API をつかって、appやin-app purchaseのpriceをコントロールする

# 目的
- (大量にある)Appやin-app purchaseをAppStoreConnectでセールのたびに値札を付け替えるのは疲れる。
- APIで一気にやれたらうれしい。

# サンプル
https://github.com/korginc/AppStoreConnectTools

# ref.
https://developer.apple.com/videos/play/wwdc2023/10014/

# 前提
- JWTをとる、とかそういうのはもうできているとする。(あとで書き加えるかもしれないが)
# 価格をつける・付け直す
- これは `priceSchedule` をつける、ということ。今の価格を変更するというよりは、価格変更のスケジュールを追加する。
-  しかるに以下のAPI エンドポイントに`POST`リクエストを送りつける、ということ。

```
https://api.appstoreconnect.apple.com/v1/appPriceSchedules
``` 
具体的には以下のようになる。
```bash
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "$JSON_PAYLOAD" \
  "https://api.appstoreconnect.apple.com/v1/appPriceSchedules"
```
`JSON_PAYLOAD`は以下のような内容。どのアプリについて、どの地域の価格をいくらに変更するかを示す必要があり、それらが記述されている。以下が注意点
- 価格には、いつから、いつまで、も含む
- 価格は即値を入れるのではなく、価格IDをいれる。これもAPIから取得する。
- 価格IDに国・地域の場所の情報が畳み込まれている。
- `POST`でリクエストすると、おそらく今設定されているものがすべて上書きになる。したがって、`PATCH`や`DELETE`は用意していないが、変更、削除は`POST`から行う。

ここで、
`$APP_ID`は該当アプリのID。以下のURLから取得できる。
```bash
curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/apps"
```
`$TERRITORY`は国・地域コード。以下のURLより。
```bash
curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/appPriceSchedules/$APP_ID/baseTerritory"
```
`${XXXX}`はAppStore Connect APIで使用可能なプレイスフォルダーで、実際の内容を`included`以下に書いている。
```json
{
  "data": {
    "type": "appPriceSchedules",
    "relationships": {
      "app": {
        "data": {
          "type": "apps",
          "id": "'$APP_ID'"
        }
      },
      "baseTerritory": {
        "data": {
          "type": "territories",
          "id": "'$TERRITORY'"
        }
      },
      "manualPrices": {
        "data": [
          {
            "type": "appPrices",
            "id": "${new-price-0}"
          },
          {
          ...
          }
        ]
      }
    }
  },
  "included": [
    {
      "id": "${new-price-0}",
      "type": "appPrices",
      "attributes": {
        "startDate": null,
        "endDate": "2025-06-01"
      },
      "relationships": {
        "appPricePoint": {
          "data": {
            "type": "appPricePoints",
            "id": "'$ID_07_99'"
          }
        }
      }
    },
    {
      "id": "${new-price-1}",
      "type": "appPrices",
      "attributes": {
        "startDate": "2025-06-01",
        "endDate": null
      },
      "relationships": {
        "appPricePoint": {
          "data": {
            "type": "appPricePoints",
            "id": "'$ID_03_99'"
          }
        }
      }
    }
  ]
}
```
`manualPrices::data`には、複数個のエントリーを入れることができる。というか、通常は（つまり、価格変更のスケジュールを行うには) 2つのエントリーが入ることになる。1つは、現在価格のエントリー。2つ目は価格変更のエントリー。ほぼ繰り返しの説明となるが、新価格のスケジュールをつける場合にAPI越しでこれを達成するためには、現在の価格に関する情報もJSONに載せないとならない。

以下`included`の説明。ここで、`${XXXX}`で定義した`manualPrices`のアイテムを記述している。上記の例では、2つのアイテムについて記述している。つまり、`2025-06-01`までの価格と、その日以降の価格である。ここでの注意点：
- `appPricePoint::data::id`の内容は、以下のURLで取得できるIDであること。この例では`territory=USA`でフィルタしているが、それぞれの`baseTerritory`で読み替えてください。
```bash
curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/apps/$APP_ID/appPricePoints?filter\[territory\]=USA&limit=100
```

## 他の例
### 設定した価格変更をキャンセルする
他、例えば、価格変更をキャンセルする場合は、現在の価格のIDをとってきて、`included`に1つのアイテムだけを登録、`startDate`と`endDate`を`null`にすれば、設定できます。

### 現在設定した価格変更の期間をキャンセルする
設定中の価格変更がどんなものであっても、結局最終的に見たい状態の価格スケジュールをPayloadに載せることがやれることの全て。なので、上の例の 6/1/2025からの変更を7/1/2025に変更したい場合は、全く同じPayloadで、`endDate`と`startDate`を`2025-07-01`に変更してPOSTします。

### ある国(たとえば日本)だけ、価格を変更する。
まず、日本のpricePointsを確認する。
```bash
curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/apps/$APP_ID/appPricePoints?filter\[territory\]=JPN&limit=100"
```
設定したいPrice pointのIDを取得して、これをPayloadに組み入れます。`manualPrices`としては2つのアイテムが必要です。一つは、グローバル・プライスの再設定。二つ目は、この日本の価格設定です。
```json
"included": [
    {
      "id": "${new-price-0}",
      "relationships": {
        "appPricePoint": {
          "data": {
            "type": "appPricePoints",
            "id": "'$ID_01_99'"
          }
        }
      },
      "type": "appPrices",
      "attributes": {
        "startDate": null,
        "endDate": null
      }
    },
    {
      "id": "${new-price-1}",
      "relationships": {
        "appPricePoint": {
          "data": {
            "type": "appPricePoints",
            "id": "'$JPY_1030'"
          }
        }
      },
      "type": "appPrices",
      "attributes": {
        "startDate": null,
        "endDate": null
      }
    }
  ]
```
日本とそれ以外のpricePoint IDをいれて、それぞれの attributeを`null`にすれば、現在の価格が直接updateされることになるでしょう。スケジュールしたい場合は、`endDate`だけ`null`にすればok

# in-app purchaseの場合
in-app purchaseの場合もそれほど変わりません。まず、変更したいin-app purchaseのIDを取得します。
```bash
curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/apps/$APP_ID/inAppPurchasesV2"
```
現在の`manualPrices`をしらべます。また、ターゲットのprice pointを決定しIDを取得します。
```bash
 curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/inAppPurchasePriceSchedules/$IAP_ID/manualPrices"

 curl -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v2/inAppPurchases/$IAP_ID/pricePoints?filter\[territory\]=USA&limit=100"
```
以下のURLにPOSTメッセージを送信して、アップデートします。
```bash
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "$JSON_IAP_PAYLOAD" \
  "https://api.appstoreconnect.apple.com/v1/inAppPurchasePriceSchedules"
```
ここで `JSON_IAP_PAYLOAD`は例えば以下のようになります。
```json
{
  "data": {
    "type": "inAppPurchasePriceSchedules",
    "relationships": {
      "inAppPurchase": {
        "data": {
          "type": "inAppPurchases",
          "id": "'$IAP_ID'"
        }
      },
      "baseTerritory": {
        "data": {
          "type": "territories",
          "id": "USA"
        }
      },
      "manualPrices": {
        "data": [
          {
            "type": "inAppPurchasePrices",
            "id": "${new-price-0}"
          },
          {
            "type": "inAppPurchasePrices",
            "id": "${new-price-1}"
          }
        ]
      }
    }
  },
  "included": [
    {
      "id": "${new-price-0}",
      "relationships": {
        "inAppPurchasePricePoint": {
          "data": {
            "type": "inAppPurchasePricePoints",
            "id": "'$IAP_06_99'"
          }
        }
      },
      "type": "inAppPurchasePrices",
      "attributes": {
        "startDate": null,
        "endDate": "2025-05-01"
      }
    },
    {
      "id": "${new-price-1}",
      "relationships": {
        "inAppPurchasePricePoint": {
          "data": {
            "type": "inAppPurchasePricePoints",
            "id": "'$IAP_03_99'"
          }
        }
      },
      "type": "inAppPurchasePrices",
      "attributes": {
        "startDate": "2025-05-01",
        "endDate": null
      }
    }
  ]
}
```
アプリの価格変更Payloadから見た違いは以下。
- app/apps となっているところは inAppPurchase/inAppPurchases
- pricePointもきちんとin-app purchaseの pricePoints API で調べてIDをゲットする。

# バッチ処理する場面において
- APP_ID や IAP_ID は調べておく。これらは後から変更されるものではない。これらを配列につめてループする。
- pricePointはもし、appやin-app purchaseで固有ならば、それらを取らないといけないが、IDにはなにやら法則性があるから、その辺りに頑張りどころがあるかもしれない。以下に調べた結果を示す。

# price point ID について
```bash
curl -s -H "Authorization: Bearer $TOKEN" "https://api.appstoreconnect.apple.com/v1/apps/$APP_ID/appPricePoints?filter\[territory\]=USA&limit=10" | grep -o '"id" : "[^"]*"' | head -5
```
上記URLなどで調べられる、price point IDは`BASE64`でエンコードされたトークンとなっている。
```json
{"s":"ID","t":"3CHAR_TERRITORY","p":"PRICE_ID"}
```
ここで、
- IDはAPP_IDやIAP_IDなど、APIから取得できるID
- 3CHAR_TERRITORYも`USA`や`JPN`など、これまでにすでに出てきているもの。
- 問題は`PRICE_ID`。これについてはundocumentedのようだが、free(つまり tier 0)のものが `10000`で、そこからtier(900種類に増えたやつ)を登っていくようだ。
## 例
| USA(USD) | PRICE_ID |
|:-:|:-:|
| 0.00  | 10000  |
|  7.99 |  10101 |
|  9.99 |  10127 |
| 13.99  | 10147  |
| 15.99  | 10157  |
| 17.99  | 10167  |
| 19.99  | 10177  |