iOSアプリ内課金をテストし尽くす〜第4回 paymentQueue関数(解約後の再契約)〜[Xcode12/iOS14]
NTTレゾナントテクノロジー アジャイルデザイン部の西添です。
この記事は連載「iOSアプリ内課金をテストし尽くす」の第4回目です。この連載では、iOSアプリ内課金(In-App Purchase)のサブスクリプションを題材に、様々な場面でStoreKitフレームワークがどのように振る舞うのかを実験結果をもとに紹介しています。実験はStoreKit Testing in Xcode(以下、Xcodeテストと呼びます)とSandboxの2つの方法で実施しています。実験対象と実験環境の詳細については第1回をご覧ください。
iOSアプリ内課金を実装する上で、SKPaymentTransactionObserverのpaymentQueue(_:updatedTransactions:)関数がどのタイミングで呼ばれ、引数のトランザクションの中身がどうなっているのかを正しく把握しておく必要があります。第4回となる本記事では、サブスクリプションを解約した後に、有効期限内と有効期限切れ後のそれぞれでサブスクリプションを再契約した場合にpaymentQueue関数がどのように振る舞うのかを紹介します。
本題に入る前に、各環境でのサブスクリプションの解約方法と再契約方法を説明しておきます。
連載目次
第1回 導入編
第2回 paymentQueue関数(購入時、更新時、重複購入時)
第3回 paymentQueue関数(Interrupted Purchase、Ask to Buy)
第4回 paymentQueue関数(解約後の再契約) ←本記事
第5回 finishTransaction()を実行しないとどうなるか
↓今後公開予定↓
第6回 購入に失敗するケース
第7回 レシート
サブスクリプションの解約方法
iOSの画面上では「解約」ではなく「サブスクリプションをキャンセルする」という表現が使われています。私は「解約」の方がしっくり来るので、本記事では「解約」と表記することにします。
サブスクリプションの解約手順はProduction環境、Sandbox環境、StoreKit Testing in Xcodeで異なります。以下では3環境それぞれでの手順を説明します。
[Production環境]
Production環境では、サブスクリプション管理画面への行き方が2通りあります。
▼[設定アプリ]>[一番上の名前が表示されている行]>[サブスクリプション]
▼[App Storeアプリ]>[右上のアイコン]>[サブスクリプション]
このサブスクリプション管理画面で「サブスクリプションをキャンセルする」をタップするとサブスクリプションを解約できます。
[Sandbox環境]
Sandbox用Apple IDのサブスクリプション管理画面への行き方は、Production環境と異なります。
▼[設定アプリ]>[App Store]>[Sandboxアカウント]>[管理]
このサブスクリプション管理画面で「サブスクリプションをキャンセルする」をタップするとサブスクリプションを解約できます。
※ iOS13以前の端末ではSandbox環境のサブスクリプションは解約できません(参考: https://developer.apple.com/jp/news/?id=nue1z72t )。
[StoreKit Testing in Xcode]
Transaction Managerで「Subscription Options」をクリックし、「Cancel Subscription」にチェックマークを付け、Doneをクリックすると、サブスクリプションを解約することができます。
サブスクリプションの再契約方法
iOSの画面上では「再契約」ではなく「サブスクリプションに再登録する」という表現が使われています。私は「再契約」の方がしっくり来るので、本記事では「再契約」と表記することにします。
サブスクリプションを解約した後に再び契約する方法としては、
・方法1. サブスクリプション管理画面で購入手続きをする
・方法2. アプリ内で購入手続きをする
の2種類あります。方法2はサブスクリプションの初回購入と同じですので割愛します。ここでは方法1の手順を説明します。
[Production環境]
サブスクリプション管理画面で再契約したいサブスクリプションを選択し、モーダルで「サブスクリプションに登録」ボタンを押すとサブスクリプションを再契約できます。
[Sandbox環境]
Sandbox用Apple IDのサブスクリプション管理画面で再契約したいサブスクリプションを選択し、モーダルで「支払い」ボタンを押すとサブスクリプションを再契約できます。
私の環境では、支払いのモーダルのアカウント欄に本物のApple IDが表示されてしまう不具合がありました。その場合は本物のApple IDをサインアウトしておくと、Sandbox Apple IDでサブスクリプションを再契約することができました。
[StoreKit Testing in Xcode]
Transaction Managerで「Subscription Options」をクリックし、再契約したいサブスクリプションにチェックマークを付け、Doneをクリックすると、サブスクリプションを再契約することができます。
場面6. サブスクリプションを解約し、有効期限前に再契約したとき
サブスクリプションを解約したけれども、有効期限内にやっぱりサブスクリプションを続けようと思い直し、契約し直すようなケースです。一言で言うと、解約のキャンセルです。
先述した通り、解約済みのサブスクリプションを再契約する方法には、(1)サブスクリプション管理画面で購入手続きをする方法と、(2)アプリ内で購入手続きをする方法の2パターンがあります。Sandboxテストでは両パターンに挙動の違いが見られます。以下では、それぞれのパターンについての実験結果をご紹介します。
場面6-1. サブスクリプション管理画面で再契約する場合
手順:
1. アプリ内で初回のサブスクリプションを購入する
2. サブスクリプションをキャンセルする
3. 有効期限が切れる前に、サブスクリプション管理画面で再契約する
要点:
・SandboxテストとProduction環境はpaymentQueue(_:updatedTransactions:)が呼ばれない
・XcodeテストではtransactionStateが.purchasedのpaymentQueue(_:updatedTransactions:)が呼ばれる
[Xcodeテスト]
Xcodeテストでは、端末側のサブスクリプション管理画面で解約や再契約はできないため、XcodeのTransaction Managerで代替します。
最初に、1回目のサブスクリプションを開始します。この例では、transactionStateが.purchasedのTransactionのTransaction IDは「0」、Original Transaction IDは「nil」であることがログからわかります。
▼初回サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>>
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<<
paymentQueue(_:updatedTransactions:) >>>>>>>>>>
---------- transaction[0] ----------
Transaction ID = Optional("0"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<<
1回目のサブスクリプション期間中にXcodeのTransaction Managerでサブスクリプションをキャンセルします。そして、1回目のサブスクリプション期間が終わる前に、Transaction Managerでサブスクリプションを再契約します。
その際に、アプリをフォアグラウンドにしていた場合は即時、アプリをバックグラウンドにしていた場合はフォアグラウンドにした瞬間にpaymentQueue(_:updatedTransactions:)が呼ばれます(transactionStateは.purchased)。この例ではTransaction IDは「1」(初回のサブスクリプションのTransaction IDとは異なる値)、Original Transaction IDは「0」(初回のサブスクリプションのTransaction IDと同じ値)です。
▼Transaction Managerでサブスクリプションをキャンセルした後、有効期限内に再契約
paymentQueue(_:updatedTransactions:) >>>>>>>>>>
---------- transaction[0] ----------
Transaction ID = Optional("1"), State = .purchased
Original Transaction ID = Optional("0"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<<
[Sandboxテスト]
最初に、1回目のサブスクリプションを開始します。
▼初回サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>>
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<<
paymentQueue(_:updatedTransactions:) >>>>>>>>>>
---------- transaction[0] ----------
Transaction ID = Optional("1000000777610476"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<<
1回目のサブスクリプション期間中に設定アプリのSANDBOXアカウントの管理画面でサブスクリプションをキャンセルします。そして、1回目のサブスクリプション期間が終わる前にサブスクリプションを再契約します。
ここからの挙動はXcodeテストと異なります。
再契約後、アプリをフォアグラウンドにしたり、アプリを再起動したりしても、paymentQueue(_:updatedTransactions:)は呼ばれません。
[Production環境]
AppStoreへリリース済みのアプリで実際にお金を支払って試したところ、Sandboxテストと同様に、paymentQueue(_:updatedTransactions:)は呼ばれないようでした。
場面6-2. アプリ内で購入手続きをする場合
手順:
1. アプリ内で初回のサブスクリプションを購入する
2. サブスクリプションをキャンセルする
3. 有効期限が切れる前に、アプリ内でサブスクリプションを購入する
要点:
・どの課金環境でも、購入手続き後にpaymentQueue(_:updatedTransactions:)が呼ばれる(transactionState=.purchased)。※これは初回購入時と同じ挙動
・再契約時のOriginal Transaction IDは、そのApple IDで初めて当該サブスクリプションを契約したときのTransaction IDと同じ値になる。
[Xcodeテスト]
最初に、1回目のサブスクリプションを開始します。この例では、TransactionのTransaction IDは「1」、Original Transaction IDは「nil」であることがログからわかります。
▼初回サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:56:47]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:56:47]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:57:10]
---------- transaction[0] ----------
Transaction ID = Optional("1"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:57:10]
1回目のサブスクリプション期間中にXcodeのTransaction Managerでサブスクリプションをキャンセルします。
そして、1回目のサブスクリプション期間が終わる前に、アプリ内で再びサブスクリプションを購入します。
ここからはこのあと紹介する場面7-2(有効期限切れ後にアプリ内で購入手続き)と同じです。
画面遷移とpaymentQueueが呼ばれるタイミングは第2回でご紹介した「場面1. 初めて購入したとき」と同じです。「You're all set. Your purchase was successful. [Environment: Xcode]」というダイアログでOKを押すとpaymentQueue(_:updatedTransactions:)が呼ばれます。このとき、transactionStateは.purchasedです。この例ではTransaction IDは「2」(初回のサブスクリプションのTransaction IDとは異なる値)、Original Transaction IDは「1」(初回のサブスクリプションのTransaction IDと同じ値)です。
▼解約手続き後、有効期限内にアプリ内でサブスクリプションを購入
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:57:53]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:57:53]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:58:08]
---------- transaction[0] ----------
Transaction ID = Optional("2"), State = .purchased
Original Transaction ID = Optional("1"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:58:08]
[Sandboxテスト]
最初に、サブスクリプションを開始します。この例では、transactionStateが.purchasedのTransactionのTransaction IDは「1000000842650692」、Original Transaction IDは「1000000786271795」であることがログからわかります。Original Transaction IDに値が入っているのは、このアカウントで過去に当該サブスクリプションを購入していたことがあるからです。
▼サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:39:15]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:39:15]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:40:10]
---------- transaction[0] ----------
Transaction ID = Optional("1000000842650692"), State = .purchased
Original Transaction ID = Optional("1000000786271795"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:40:10]
サブスクリプション期間中に設定アプリのSANDBOXアカウントの管理画面でサブスクリプションをキャンセルします。そして、サブスクリプションの期間が終わる前に、アプリ内で再びサブスクリプションを購入します。
画面遷移とpaymentQueueが呼ばれるタイミングは第2回でご紹介した「場面1. 初めて購入したとき」と同じです。
「完了しました。購入手続きが完了しました。[Environment: Sandbox]」というダイアログでOKを押すとpaymentQueue(_:updatedTransactions:)が呼ばれます。このとき、transactionStateは.purchasedです。この例ではTransaction IDは「1000000842650692」、Original Transaction IDは「1000000786271795」です。Transaction IDとOriginal Transaction IDは両方ともサブスクリプションを開始したときのTransaction(上の18:40:10のログ)と同じ値になっています。
▼解約手続き後、有効期限内にアプリ内でサブスクリプションを購入
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:41:01]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:41:01]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [18:41:35]
---------- transaction[0] ----------
Transaction ID = Optional("1000000842650692"), State = .purchased
Original Transaction ID = Optional("1000000786271795"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [18:41:35]
[Production環境]
AppStoreへリリース済みのアプリで実際にお金を支払って試したところ、「完了しました。購入手続きが完了しました。」というダイアログが表示され、それと同時に裏でpaymentQueue(_:updatedTransactions:)が呼ばれていました。Sandboxテストに近い挙動です。
場面7. サブスクリプションを解約し、有効期限切れ後に再契約したとき
サブスクリプションを契約していたことがあるユーザが、サブスクリプションを再開するようなケースです。
要点:
・XcodeテストとSandboxテストで差異は無い。
・再契約後にpaymentQueue(_:updatedTransactions:)が呼ばれる(transactionState=.purchased)。
※サブスクリプション管理画面で再契約する場合は、アプリをフォアグラウンドにした時(システムダイアログは表示されない)
※アプリ内で購入手続きをする場合は、購入手続き直後
・再契約時のOriginal Transaction IDは、そのApple IDで初めて当該サブスクリプションを契約したときのTransaction IDと同じ値になる。
・再契約時のTransaction IDは1回目の契約時とは異なる値になる。
場面7-1. サブスクリプション管理画面で再契約する場合
手順:
1. アプリ内で初回のサブスクリプションを購入する
2. サブスクリプションをキャンセルする
3. 有効期限が切れる
4. サブスクリプション管理画面で再契約する
[Xcodeテスト]
最初に、1回目のサブスクリプションを開始します。この例では、transactionStateが.purchasedのTransactionのTransaction IDは「12」、Original Transaction IDは「nil」であることがログからわかります。
▼初回サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:31:57]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:31:57]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:32:33]
---------- transaction[0] ----------
Transaction ID = Optional("12"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:32:33]
1回目のサブスクリプション期間中にXcodeのTransaction Managerでサブスクリプションをキャンセルします。そしてアプリをバックグラウンドにして、有効期限を過ぎてから、Transaction Managerでサブスクリプションを再開させます。
ここからは場面6-1(有効期限内に管理画面で購入手続き)と同じです。
その後、アプリをフォアグラウンドにすると、paymentQueue(_:updatedTransactions:)が呼ばれます。このとき、transactionStateは.purchasedです。また、この例ではTransaction IDは「13」、Original Transaction IDは「12」です。このように、2回目以降の購入時には、Original Transaction IDには初回のサブスクリプションのTransaction IDが入ります。この点がiOSのアプリ内課金の特徴です。なお、Transaction IDには別の値が入ります。
▼有効期限が切れた後にTransaction Managerで再契約
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:34:47]
---------- transaction[0] ----------
Transaction ID = Optional("13"), State = .purchased
Original Transaction ID = Optional("12"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:34:47]
[Sandboxテスト]
最初に、1回目のサブスクリプションを開始します。この例では、transactionStateが.purchasedのTransactionのTransaction IDは「1000000859407587」、Original Transaction IDは「nil」であることがログからわかります。Original Transaction IDは「nil」なのは、このアカウントで過去に当該サブスクリプションを購入したことがないからです。
▼初回サブスクリプション開始
printReceiptDetails() <<<<<<<<<< [17:37:46]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [17:37:48]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [17:37:48]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [17:38:10]
---------- transaction[0] ----------
Transaction ID = Optional("1000000859407587"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [17:38:10]
1回目のサブスクリプション期間中に設定アプリのSANDBOXアカウントの管理画面でサブスクリプションをキャンセルします。そして、有効期限を過ぎてから、設定アプリのSANDBOXアカウントの管理画面で再びサブスクリプションを購入します。
その後、アプリをフォアグラウンドにすると、paymentQueue(_:updatedTransactions:)が呼ばれます。このとき、transactionStateは.purchasedです。この例ではTransaction IDは「1000000859408375」(初回のサブスクリプションのTransaction IDとは異なる値)、Original Transaction IDは「1000000859407587」(初回のサブスクリプションのTransaction IDと同じ値)です。
paymentQueueは呼ばれても、システムダイアログは表示されません。
▼有効期限が切れた後にサブスクリプション管理画面で購入し、アプリをフォアグラウンドにする
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [17:46:12]
---------- transaction[0] ----------
Transaction ID = Optional("1000000859408375"), State = .purchased
Original Transaction ID = Optional("1000000859407587"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [17:46:12]
場面7-2. アプリ内で購入手続きをする場合
手順:
1. アプリ内で初回のサブスクリプションを購入する
2. サブスクリプションをキャンセルする
3. 有効期限が切れる
4. アプリ内でサブスクリプションを購入する
[Xcodeテスト]
最初に、1回目のサブスクリプションを開始します。この例では、transactionStateが.purchasedのTransactionのTransaction IDは「1」、Original Transaction IDは「nil」であることがログからわかります。
▼初回サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:22:32]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:22:32]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:22:45]
---------- transaction[0] ----------
Transaction ID = Optional("1"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:22:45]
1回目のサブスクリプション期間中にXcodeのTransaction Managerでサブスクリプションをキャンセルします。そして、有効期限を過ぎてから、アプリ内で再びサブスクリプションを購入します。
ここからは場面6-2(有効期限内にアプリ内で購入手続き)と同じです。
画面遷移とpaymentQueueが呼ばれるタイミングは第2回でご紹介した「場面1. 初めて購入したとき」と同じです。「You're all set. Your purchase was successful. [Environment: Xcode]」というダイアログでOKを押すとpaymentQueue(_:updatedTransactions:)が呼ばれます。このとき、transactionStateは.purchasedです。この例ではTransaction IDは「2」(初回のサブスクリプションのTransaction IDとは異なる値)、Original Transaction IDは「1」(初回のサブスクリプションのTransaction IDと同じ値)です。
▼有効期限が切れた後にアプリ内でサブスクリプションを購入
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:23:35]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:23:35]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [15:23:54]
---------- transaction[0] ----------
Transaction ID = Optional("2"), State = .purchased
Original Transaction ID = Optional("1"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [15:23:54]
[Sandboxテスト]
最初に、1回目のサブスクリプションを開始します。この例では、transactionStateが.purchasedのTransactionのTransaction IDは「1000000858789433」、Original Transaction IDは「nil」であることがログからわかります。Original Transaction IDは「nil」なのは、このアカウントで過去に当該サブスクリプションを購入したことがないからです。
▼初回サブスクリプション開始
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [16:38:37]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [16:38:37]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [16:39:05]
---------- transaction[0] ----------
Transaction ID = Optional("1000000858789433"), State = .purchased
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [16:39:05]
1回目のサブスクリプション期間中に設定アプリのSANDBOXアカウントの管理画面でサブスクリプションをキャンセルします。そして、有効期限を過ぎてから、アプリ内で再びサブスクリプションを購入します。
画面遷移とpaymentQueueが呼ばれるタイミングは第2回でご紹介した「場面1. 初めて購入したとき」と同じです。
「完了しました。購入手続きが完了しました。[Environment: Sandbox]」というダイアログでOKを押すとpaymentQueue(_:updatedTransactions:)が呼ばれます。このとき、transactionStateは.purchasedです。この例ではTransaction IDは「1000000858818688」(初回のサブスクリプションのTransaction IDとは異なる値)、Original Transaction IDは「1000000858789433」(初回のサブスクリプションのTransaction IDと同じ値)です。
▼有効期限が切れた後にアプリ内でサブスクリプションを購入
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [17:10:55]
---------- transaction[0] ----------
Transaction ID = nil, State = .purchasing
Original Transaction ID = nil, State = nil
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [17:10:55]
paymentQueue(_:updatedTransactions:) >>>>>>>>>> [17:11:30]
---------- transaction[0] ----------
Transaction ID = Optional("1000000858818688"), State = .purchased
Original Transaction ID = Optional("1000000858789433"), State = Optional(".purchased")
transaction.error = nil
paymentQueue(_:updatedTransactions:) <<<<<<<<<< [17:11:30]
宣伝
NTTレゾナントテクノロジーでは一緒に働いてくれるAndroid/iOSアプリエンジニアを募集中です。もし興味がありましたら採用ページをご覧ください。
この記事が気に入ったらサポートをしてみませんか?