App Store のアプリ商品ページを下にスクロールすると、スクリーンショットの近くに「アプリ内課金」の一覧が出ることがあります。ここに自分の課金アイテムを並べ、タップした人をアプリ内の購入フローへ直接送り込めるのが、プロモーション IAP(Promoted In-App Purchase)です。
私自身、個人開発で壁紙アプリや癒し系アプリを運営していて、課金導線はアプリを開いた後のペイウォールに頼りきっていました。けれど商品ページのプロモーション枠は、まだアプリを入れていない人にも課金アイテムを見せられる、数少ない「インストール前の接点」です。ここを設計するとき一番つまずくのは、購入ボタンがアプリの外側で押されるという点でした。
つまり、購入の意思表示はアプリが起動していない瞬間に発生します。場合によってはアプリのインストールすら終わっていません。この「外側から飛んでくる購入」をどう受け止めるかが、プロモーション IAP の設計の中心になります。
商品ページのタップから購入完了までに何が起きるか
利用者が App Store の商品ページでプロモーション枠の課金アイテムをタップすると、次の順序で処理が進みます。
- アプリが未インストールなら、まず App Store がインストールを促します
- インストール後(または既にある場合)、アプリが起動します
- StoreKit が「この商品を買おうとしている」というイベントをアプリに渡します
- アプリ側がそれを受け取り、購入処理を続行するか保留するかを決めます
ここで重要なのは、4 の判断権がアプリ側にあることです。StoreKit は購入を強制せず、「今すぐ進めてよいか」をアプリに尋ねてきます。アプリの初期化が終わっていない、利用者がまだログインしていない、といった状況では、ここでいったん保留し、準備が整ってから購入を再開できます。この保留と再開の仕組みを正しく組まないと、起動直後にいきなり購入シートが出て利用者が混乱したり、逆にイベントを取りこぼして「タップしたのに何も起きない」状態になります。
App Store Connect 側の準備
実装の前に、課金アイテムをプロモーション対象として登録しておく必要があります。
- App Store Connect で対象の App を開き、「アプリ内課金」から個別のアイテムを選びます
- 「App Store でプロモーション」を有効にします
- 1024×1024 のプロモーション画像を登録します(これが商品ページに表示されます)
- 表示順は API からも上書きできますが、まずは Connect 上の並び順が初期値になります
プロモーション IAP は最大 20 個まで登録できます。私の経験では、最初から全部を出すより、主力のサブスクリプションや「全機能解放」のような分かりやすい 1〜2 個に絞ったほうが、商品ページが散らからず効果を測りやすいです。
react-native-iap で遅延購入を受け止める
標準の Rork アプリは React Native(Expo)で生成されるため、課金まわりは react-native-iap のような native module を開発ビルドに組み込んで扱うのが現実的です。プロモーション IAP では、購入イベントのリスナーを「アプリの初期化のなるべく早い段階」で登録するのが肝心です。リスナー登録が遅れると、コールド起動時に飛んでくるイベントを取りこぼします。
// promotedPurchase.ts — アプリ起動の最初期で呼ぶ
import {
initConnection,
getPromotedProductIOS,
requestPurchaseOnPromotedProductIOS,
promotedProductListenerIOS,
} from 'react-native-iap';
// 「準備完了か」を外から差し込めるようにしておく
type ReadyCheck = () => boolean;
let pendingPromoted = false;
export async function setupPromotedPurchase(isReady: ReadyCheck) {
await initConnection();
// App Store からプロモーション購入が飛んできたときに発火する
promotedProductListenerIOS(async () => {
pendingPromoted = true;
await tryConsumePromoted(isReady);
});
// コールド起動直後、すでにイベントが来ていた場合の取りこぼし対策
const promoted = await getPromotedProductIOS();
if (promoted) {
pendingPromoted = true;
await tryConsumePromoted(isReady);
}
}
// 準備が整っていれば購入を実行、未完了なら保留したまま戻る
export async function tryConsumePromoted(isReady: ReadyCheck) {
if (!pendingPromoted) return;
if (!isReady()) return; // ログイン・初期化が未完了なら保留を維持
pendingPromoted = false;
await requestPurchaseOnPromotedProductIOS();
}
ポイントは tryConsumePromoted を二箇所から呼べるようにしておくことです。ひとつはイベント受信時、もうひとつは「準備が整った瞬間」です。たとえばログイン完了後やオンボーディング通過後に同じ関数を呼べば、保留していた購入がそのタイミングで自然に再開されます。
// ログイン完了時に保留中の購入を再開する
async function onLoginSuccess() {
await tryConsumePromoted(() => true);
}
保留と再開を支える「準備完了」の定義
ここが設計の本体です。「準備完了(isReady)」を何と定義するかで、利用者の体験が変わります。私が運用していて落ち着いたのは、次の三条件をすべて満たしたときだけ購入を進める形でした。
| 条件 | 満たさない場合の挙動 |
| StoreKit の初期化(initConnection)完了 | 購入要求が無視されるため必ず待つ |
| 利用者アカウントの確定(ログインまたはゲスト確定) | 購入を保留し、ログイン後に再開 |
| 購入結果を反映する画面が描画可能 | 保留し、ナビゲーション準備後に再開 |
この三つを満たすまでイベントを pendingPromoted に保持し続けるのが、取りこぼしを防ぐ一番確実な方法でした。逆に言えば、保留状態をメモリ上に持つだけだとアプリが落ちると消えます。コールド起動の取りこぼし対策として getPromotedProductIOS を起動時に必ず一度呼ぶ、という二重化が効きます。
Rork Max(ネイティブ Swift)の場合は StoreKit 2 で受ける
Rork Max はネイティブ Swift アプリを生成するため、StoreKit 2 の PurchaseIntent で受け取れます。こちらは非同期シーケンスで購入意図を待ち受ける、より素直な形です。
import StoreKit
// アプリ起動直後に監視タスクを張る
func observePromotedPurchases() {
Task.detached {
for await intent in PurchaseIntent.intents {
await handlePromoted(intent.product)
}
}
}
func handlePromoted(_ product: Product) async {
// 準備が整うまで待つ(ログイン確定など)
await AppReadiness.shared.waitUntilReady()
do {
let result = try await product.purchase()
await fulfill(result) // 課金反映
} catch {
// ネットワークやキャンセルはここで握りつぶさず記録する
Telemetry.log("promoted_purchase_failed", error)
}
}
StoreKit 1 系の shouldAddStorePayment に相当する「保留したいときは false を返す」挙動は、StoreKit 2 では「intent を受け取ってから準備完了まで待つ」という素直な待機に置き換わります。Rork Max が生成するのはこの監視タスクの足場までで、waitUntilReady に何を入れるか、失敗をどう記録するかは自分で詰める部分でした。
審査と計測で弾かれやすい点
プロモーション IAP は App Store の審査でも見られます。私が踏んだり聞いたりした範囲で、つまずきやすいのは次の点です。
- タップしてもアプリ側で自前のペイウォールを挟むのは避けます。Apple は「商品ページで選んだ商品をそのまま購入できる」ことを期待しています。間に独自の課金訴求画面を割り込ませると、本来の購入対象とずれてリジェクト理由になり得ます
- 未ログインで購入を完全に止めてしまうと、審査時に「購入できない」と判定される恐れがあります。ゲストでも購入し、後からアカウントに紐づける逃げ道を用意しておくと安全です
- 計測は通常の in-app purchase と同じ経路に乗りますが、流入元がプロモーション枠かどうかを自前のイベントで区別しておくと、商品ページ経由の課金率を後から評価できます。私は購入完了イベントに
source: "promoted" を付けて、通常のペイウォール経由と分けて見ています
収益導線として見ると、プロモーション IAP は「アプリを開く前の課金接点」という独自の位置にあります。アプリ内のペイウォールと違って自由な訴求はできませんが、検索からアプリ商品ページに来た瞬間の購入意欲を、インストール後まで途切れさせずに運べるのが価値です。保留と再開の設計さえ丁寧に組めば、取りこぼしの少ない静かな収益経路になります。実装の参考になれば幸いです。