子ども向けの学習アプリで月額サブスクリプションを実装し、リリースして数週間が経ったある日、App Store Connect の売上ダッシュボードを開いて違和感を覚えたことがあります。前日に確かに発生していた数件の月額課金が、翌日には消えているのです。
最初は単純なキャンセルだろうと思いました。けれども RevenueCat のダッシュボードを開いてみると、「Refunded」のステータスで戻されているレコードが並んでいて、そのほとんどが Family Sharing 経由の購入でした。
子どもアカウントから購入リクエストが飛び、親が Ask to Buy(承認購入)で一度許可したものの、後から App Store のサポートに連絡して払い戻しを受けていたのです。アプリ側はすでに会員権限を付与してコンテンツを提供してしまっており、私が状況に気づいたのは翌週の集計を見たときでした。
このパターンは、Family Sharing と Ask to Buy の挙動を理解しないまま StoreKit を組むと、ほぼ必ずどこかで踏むことになります。ここではFamily Sharing が IAP 収益にもたらす4つのトリガーと、Rork で開発したアプリで安全に動かすための実装パターンを、実際のコードとともに整理します。
「子どもが買ったはずなのに翌日消えていた」— Family Sharing の返金が起きる場面
Family Sharing は親と最大5人の家族が、購入したアプリ内コンテンツを共有できる仕組みです。便利な機能ですが、開発者からすると次のような独特の挙動があります。
子どもアカウントが購入した場合、支払いは親のクレジットカードで行われる
親は購入後でも App Store サポートに連絡して、自分の知らない購入として払い戻しを請求できる
Family Sharing が有効な商品では、購入したユーザーだけでなく家族全員が利用できる
家族のうち誰かが Family Sharing から離脱すると、その瞬間にエンタイトルメントが失効する
つまり「いったん購入が成立した売上」も、返金や離脱で後からひっくり返る可能性があるのです。これを前提に IAP を設計しないと、サーバー側でユーザーに付与した会員権限と、Apple 側の購入状態がずれていきます。ずれが蓄積すると、本来は会員ではないユーザーがプレミアム機能を使い続けてしまったり、逆にきちんと払い続けている家族メンバーが急に締め出されたりするのです。
私はこの問題を最初に踏んだとき、StoreKit のテストを Sandbox で1ユーザー分しかやっていませんでした。Family Sharing は Sandbox でも擬似的に再現できるのですが、その手順を知らないまま「決済画面が開いて、領収書が返ってきたから OK」と判断していたのです。
Family Sharing で IAP が消える4つのトリガー
実際にプロダクションで遭遇した「課金が消える」シーンは、大きく4つに分類できます。
第1のトリガーは「Ask to Buy 後の親による返金リクエスト」です。子どもアカウントから購入が要求され、親が一度承認したあと、App Store サポート経由で「子どもが勝手に買ったので返金してほしい」と連絡し、Apple が裁量で払い戻しを行うパターンです。アプリには事後的に REFUND 通知が届きます。
第2のトリガーは「Family Sharing からの脱退」です。家族メンバーが Family Sharing グループから抜けると、その瞬間に共有されていたサブスクリプションのエンタイトルメントが失効します。この場合、サブスクリプション自体は継続しているため EXPIRED ではなく REVOKE 系のイベントが飛んできます。
第3のトリガーは「Apple による自動返金(Auto-Refund)」です。新しい iOS バージョンや App Store ポリシー変更後に、不正検知ルールが厳しくなり、Apple がアプリへの確認なしに払い戻しを実行することがあります。これは Family Sharing に限らず発生しますが、家族向けのアプリでは特に発生率が高く感じます。
第4のトリガーは「親アカウントの請求トラブル」です。親のクレジットカードが期限切れや残高不足になった場合、Family Sharing 共有メンバー全員のエンタイトルメントが連動して停止します。この場合は DID_FAIL_TO_RENEW の通知が届くものの、エンドユーザーから見ると「家族の誰かのカードが原因で自分の課金が止まった」という分かりにくい状況が発生します。
これら4つを単独で扱うと実装が分散しがちなので、私は「Family Sharing に関する状態変更は、すべて Server Notifications V2 で集約して処理する」という方針を取っています。クライアント側の StoreKit 2 はあくまでユーザー体験のために使い、収益の信頼できる単一情報源(Single Source of Truth)はサーバー側に置くべきだ、という割り切りです。
App Store Connect で Family Sharing 共有商品を設定する
Family Sharing 対応の IAP を作るには、まず App Store Connect 側で商品の設定を変えておく必要があります。これを忘れていると、「実装は完璧なのに家族で共有されない」という残念な事態が起きます。
App Store Connect の「アプリ内課金」ページで対象の商品を開き、「Family Sharing」セクションをオンにします。対応している商品種別は次のとおりです。
自動更新サブスクリプション(Auto-Renewable Subscriptions)
非消費型アイテム(Non-Consumable)
逆に「消費型アイテム」(コインやチケットなど)と「非自動更新サブスクリプション」は Family Sharing に対応していません。子ども向けアプリでよくある「家族プラン」を作るなら、自動更新サブスクリプションを Family Sharing 対応で用意するのが基本になります。
注意点として、いったん有効化した Family Sharing 設定を後から無効化することはできません。既存の購入者に対する後方互換性が保てなくなるため、Apple が制約をかけているのです。サブスクリプション設計の段階で、Family Sharing を有効にするかどうかを慎重に決めておく必要があります。
価格設計についても触れておきましょう。Family Sharing 対応の商品は1人分の価格で家族全員が利用できるので、利用者が多い分だけ ARPU(1ユーザーあたり売上)が下がります。私は子ども向けの英語学習アプリで、月額500円の商品を Family Sharing 対応にしたところ、1家族で平均2.7人が使っていることが GA4 の app_user_id 分布から判明しました。実質的な ARPU は約185円まで下がっていた計算です。これを承知の上で、価格を月額780円に上げる、あるいは家族プランとは別にソロプランを用意する、といった調整を後から行いました。
設計でぶれないために、私はメモに「Family Sharing は『家族全体に課金してもらう』戦略であり、『一人あたりの収益最大化』ではない」と書いて貼っています。判断軸が定まっていないと、リリース後の売上を見て「単価を上げたいから Family Sharing をやめたい」と思ってしまうのですが、設定上の制約でそれは事実上不可能なのです。
StoreKit 2 で Family Sharing イベントを検知する
クライアント側でも、Family Sharing による購入や脱退を検知して UI に反映する必要があります。StoreKit 2 では Transaction の ownershipType プロパティで、その購入が「自分自身の購入か、家族から共有されたものか」を判定できます。
下記は、購入直後に Family Sharing 由来のトランザクションを判定し、ローカル状態とサーバー検証の両方をトリガーする実装例です。
import StoreKit
final class PurchaseManager : ObservableObject {
@Published var isFamilyShared: Bool = false
@Published var isPremium: Bool = false
private var updateListenerTask: Task< Void , Error > ?
init () {
// バックグラウンドで Transaction.updates を購読し、Family Sharing 由来の付与・取り消しを検知する
updateListenerTask = listenForTransactions ()
}
deinit {
updateListenerTask ? . cancel ()
}
/// 期待動作: アプリ起動中に Family Sharing 共有・脱退・返金が発生した瞬間にこのストリームに通知が流れる
/// 既存購入の検証は `Transaction.currentEntitlements` で別途行う
private func listenForTransactions () -> Task< Void , Error > {
Task. detached { [ weak self ] in
for await result in Transaction.updates {
guard let self else { return }
guard case . verified ( let transaction) = result else {
// 改ざん疑いのトランザクションは finish せず無視する
continue
}
await self . handleTransaction (transaction)
await transaction. finish ()
}
}
}
private func handleTransaction ( _ transaction: Transaction) async {
let isFromFamily = transaction.ownershipType == .familyShared
await MainActor. run {
self .isFamilyShared = isFromFamily
// revocationDate が入っていれば家族離脱・返金で取り消されたと判定する
self .isPremium = transaction.revocationDate == nil
}
// サーバーに JWS Signed Transaction を送り、最終判定はサーバー側に任せる
do {
try await PremiumServerClient.shared. verify ( jws : transaction.jsonRepresentation)
} catch {
// ネットワーク失敗時はローカル状態を保ち、次回起動時に再検証する
print ( "server verify failed: \( error. localizedDescription ) " )
}
}
}
このコードのポイントは3つあります。1つ目は Transaction.updates を継続的に購読することで、アプリの起動中に発生した Family Sharing 由来のイベント(共有開始・脱退・返金)をリアルタイムで受け取れることです。2つ目は ownershipType を見て家族共有の購入かどうかを区別していることで、これにより UI に「家族プランを利用中」のバッジを出すなどの差別化ができます。3つ目は revocationDate をチェックして、家族離脱や返金で取り消されたケースを検出していることです。
注意したいのは、この StoreKit 2 ハンドリングだけで会員権限を判定してはいけないという点です。アプリが起動していない間に発生したイベント(例: 親が深夜に返金リクエストを出した)はクライアントには届きません。あくまでも UX を最新に保つためのレイヤーであり、最終的な権限判断はサーバー側で App Store Server Notifications V2 を待ち受けて行うのが鉄則です。
Ask to Buy(承認購入)のフローを正しく扱う
子どもアカウントが Family Sharing で購入を試みると、Ask to Buy のフローが起動して購入リクエストが親に送られます。このとき StoreKit から見ると、Product.purchase() の戻り値が次のように分岐します。
.pending — 親の承認待ち。トランザクションはまだ確定していない
.success(.verified(transaction)) — 親が承認し、トランザクションが確定した
.userCancelled — 子どもが UI でキャンセルした
ここでよくあるミスが、.pending を .success 同等に扱ってしまうケースです。承認待ちの状態でユーザーに「購入完了!」と表示してしまうと、その後親が拒否した場合に整合性が壊れます。私が最初に作ったプロトタイプでも、この実装ミスでテストアカウントの権限が二重に付与される事故が起きました。
正しい実装は次のとおりです。
func purchase ( _ product: Product) async throws -> PurchaseResult {
let result = try await product. purchase ()
switch result {
case .pending :
// 親の承認待ち。UI は「保護者の承認をお待ちしています」状態で待機する
// 実際の権限付与は Transaction.updates ストリームで承認後に行う
return .awaitingApproval
case . success ( let verification) :
guard case . verified ( let transaction) = verification else {
// JWS 検証失敗。改ざん可能性があるので付与しない
throw PurchaseError.unverified
}
await transaction. finish ()
return . completed (transaction)
case .userCancelled :
return .cancelled
@unknown default:
// StoreKit の将来拡張への防御策
return .unknown
}
}
ポイントは .pending の場合に「承認待ち」状態で UI を停止させ、決して権限を付与しないことです。承認後に飛んでくる Transaction.updates のイベントで初めてサーバー検証→権限付与のフローを動かします。
UI 側では、.pending の状態を「保護者の承認をお待ちしています」と表示し、ユーザーがアプリを閉じても次回起動時に状態を確認できるよう、ローカルに pending な購入リクエストを保存しておくのがおすすめです。私のアプリでは UserDefaults に pendingPurchaseProductId を保存し、起動時に Transaction.currentEntitlements でステータスを再確認しています。
サーバーサイド検証 — Server Notifications V2 で Family Sharing イベントを処理する
クライアント側だけで状態を管理すると、すでに述べたとおりアプリ未起動中のイベントを取りこぼします。Apple は2021年から App Store Server Notifications V2 を提供しており、サブスクリプションの発生・更新・返金・家族共有の変更などを、すべてサーバー間通信で受け取ることができます。
V2 の通知タイプのうち、Family Sharing 関連で特に重要なものは次のとおりです。
DID_RENEW — 自動更新が成功した(Family Sharing 経由でも発火する)
REFUND — 返金が確定した
REVOKE — Family Sharing からの脱退などでエンタイトルメントが取り消された
DID_FAIL_TO_RENEW — 親の支払い失敗で更新できなかった
EXPIRED — サブスクリプションが満了した
Cloudflare Workers で V2 通知を受けるエンドポイントの実装例を示します。Rork で作ったアプリの会員サーバーをこの上に載せる前提です。
import { jwtVerify, importJWK } from 'jose' ;
interface Env {
KV_PREMIUM : KVNamespace ;
APP_STORE_BUNDLE_ID : string ;
}
export default {
async fetch ( request : Request , env : Env ) : Promise < Response > {
if (request.method !== 'POST' ) {
return new Response ( 'method not allowed' , { status: 405 });
}
const body = await request. json <{ signedPayload : string }>();
const payload = await verifyAndDecodeJWS (body.signedPayload);
// 期待動作: notificationType が REFUND/REVOKE のときは KV から該当ユーザーのプラン権限を即座に取り消す
const { notificationType , subtype , data } = payload;
const transactionInfo = await verifyAndDecodeJWS (data.signedTransactionInfo);
const userKey = `user:${ transactionInfo . appAccountToken }` ;
switch (notificationType) {
case 'DID_RENEW' :
case 'SUBSCRIBED' :
await env. KV_PREMIUM . put (userKey, JSON . stringify ({
status: 'active' ,
ownershipType: transactionInfo.inAppOwnershipType, // FAMILY_SHARED か PURCHASED か
expiresAt: transactionInfo.expiresDate,
}));
break ;
case 'REFUND' :
case 'REVOKE' :
// Family Sharing 脱退や返金 — 即座に権限を剥奪する
await env. KV_PREMIUM . put (userKey, JSON . stringify ({
status: 'revoked' ,
revokedAt: Date. now (),
reason: notificationType,
}));
await notifyClientToRevalidate (userKey);
break ;
case 'DID_FAIL_TO_RENEW' :
// 親の支払い失敗。grace period 中は権限を維持し、ユーザーに通知だけ送る
await env. KV_PREMIUM . put (userKey, JSON . stringify ({
status: 'grace_period' ,
retryUntil: transactionInfo.gracePeriodExpiresDate,
}));
break ;
case 'EXPIRED' :
await env. KV_PREMIUM . delete (userKey);
break ;
}
return new Response ( 'ok' , { status: 200 });
} ,
} ;
async function verifyAndDecodeJWS ( jws : string ) : Promise < any > {
// 実装では Apple の Root CA を使って x5c チェーンを検証する必要がある
// 簡略化のため、本番では node-apple-receipt-verify などの実装を参照する
const [, payload ] = jws. split ( '.' );
return JSON . parse ( atob (payload));
}
async function notifyClientToRevalidate ( userKey : string ) : Promise < void > {
// クライアントに Web Push などで「次回起動時にエンタイトルメントを再検証してください」と伝える
}
この実装の核心は、REFUND と REVOKE を受け取った瞬間にサーバー側の権限を即座に取り消すことです。クライアントからの問い合わせは KV を見るので、次回 API リクエストが来た時点でユーザーは無料プランに戻ります。Family Sharing からの脱退の場合は脱退した本人のみが取り消され、購入者本人や他の家族メンバーには影響しません(appAccountToken が個別のユーザー識別子になっているためです)。
実装で間違えやすいのが appAccountToken の扱いです。これは購入時にクライアントから StoreKit に渡す UUID で、Family Sharing で共有された場合でも家族メンバーごとに異なる値が記録されます。サーバー側でユーザーを一意に特定するには、購入時に appAccountToken をアプリ内のユーザー ID にマッピングして KV に保存しておく必要があります。
RevenueCat 経由の場合の挙動と注意点
RevenueCat を使うと上記のサーバー実装の大部分を肩代わりしてくれますが、Family Sharing に関しては独自の挙動があるので押さえておきましょう。
RevenueCat ダッシュボードでは、Family Sharing 経由の購入は「Family Shared」として別カテゴリで集計されます。これは customerInfo.entitlements.active[id].ownershipType で取得できます。私が運用している学習アプリでは、Family Sharing 由来のユーザーには「家族プラン中」のバッジを表示し、解約導線を「家族プラン管理ページへ」に切り替えています。これは「自分が買った覚えがないのに支払いだけ請求されている」と勘違いされて、低評価レビューにつながるのを防ぐ狙いです。
注意点として、RevenueCat の EntitlementInfo.willRenew フラグは Family Sharing 由来のエンタイトルメントでは常に true になります。これは家族メンバーから見ると、購入者本人がサブスクリプションを継続しているかどうかが分からないためです。クライアント側で「自動更新を停止しますか?」のような UI を出すと、Family Sharing メンバーには無効な操作になってしまうので、ownershipType == .familyShared の場合は管理 UI を出さないように分岐するのが安全です。
RevenueCat の Webhook で Family Sharing 関連のイベントを受け取る場合、event.type が EXPIRATION や CANCELLATION で発火します。subscriber_attributes の中に $familySharedFromUserId が入っていれば、それは家族共有由来であることを示しています。私のサーバーでは、Family Sharing 由来のキャンセルが発生したときは購入者本人の解約フォームには誘導せず、別の Slack チャネルに通知して状況を確認できるようにしました。
RevenueCat を使うか自前で Server Notifications V2 を組むかは、月額収益のレンジで判断するのが現実的です。RevenueCat の固定費が気になり始めたら自前運用への移行を検討する流れについては、RevenueCat の固定費が気になり始めた人のための、App Store Server Notifications V2 自前運用ガイド で詳しく書きました。
よくある実装ミスと落とし穴 — 私が遭遇した4つの実例
ここからは、Rork で作ったアプリで実際に踏んだ落とし穴を紹介します。先に知っていれば数日のデバッグを節約できる内容です。
1つ目の落とし穴は「ownershipType を見ずに、すべての検証成功トランザクションで権限付与してしまう」ケースです。Family Sharing 対応にしたつもりが、実は家族メンバー全員に「あなたが購入したサブスクリプション」として表示してしまい、解約フォームから家族全員が解約を試みて混乱しました。ownershipType == .familyShared のときはサブスクリプション管理画面を表示せず、代わりに「家族プランを利用中です。管理は購入者の方にお願いします」と表示するのが正解です。
2つ目の落とし穴は「Sandbox で Family Sharing をテストせずにリリースしてしまう」ケースです。Sandbox では1つの Apple ID で Settings > App Store > Sandbox Account を切り替えてテストしますが、Family Sharing は複数アカウントを擬似的に家族として登録する必要があり、設定が少し複雑です。設定を後述しますが、Sandbox での Family Sharing テストを省略すると、本番で初めて挙動を見ることになり、リリース直後にレビュー欄が荒れます。
3つ目の落とし穴は「Transaction.updates を購読するタスクをアプリ起動の最初期に開始していない」ケースです。私は最初、onAppear でストリームを開始していたのですが、これだとユーザーが画面遷移する前に届いたトランザクションを取りこぼします。@main で App 構造体が初期化された直後、もしくは AppDelegate の application(_:didFinishLaunchingWithOptions:) の中で開始するのが正解です。
4つ目の落とし穴は「REFUND 通知を受けてもクライアントに反映されない」ケースです。サーバー側で権限を取り消しても、クライアントが KV を再フェッチしないと UI 上では会員のままになってしまいます。これを解決するために、サーバー側で REFUND を受けたら APNs サイレント Push を送るか、もしくは API レスポンスに ETag を付けて、クライアントが古いキャッシュを使い続けないように設計しています。
これらのミスを事前にチェックリスト化することで、リリース後のサポート対応が大幅に減りました。Family Sharing は機能としては魅力的ですが、エッジケースを潰す手間が一般的な単独サブスクリプションの2倍はかかると見積もっておくのが現実的です。
Sandbox で Family Sharing をシミュレートする
実機での Family Sharing テストは、Sandbox の専用 Apple ID を最低2つ用意する必要があります。手順は次のとおりです。
1. App Store Connect の「Users and Access」→「Sandbox Testers」で2つのテスター
アカウントを作成する(例: parent@test.com / child@test.com)。
両方とも同じ国・通貨に設定すること。
2. テスト用 iPhone の「設定」→「ファミリー」→「ファミリーを設定」で、parent
アカウントをファミリーオーガナイザーとして登録する。
3. 「ファミリーメンバーを追加」から child アカウントを子どもとして追加する。
この設定は本物の Apple ID ではなく Sandbox 専用 Apple ID で行うこと。
4. child アカウントの「設定」→「Screen Time」→「コンテンツとプライバシー」で
「Ask to Buy」を有効化する。
5. アプリ内で child アカウントから購入を試み、parent アカウントで承認する。
Sandbox での Family Sharing は「課金商品が共有される」「Ask to Buy が動作する」「家族メンバーの脱退で REVOKE が飛ぶ」といった本番相当の挙動を、すべて再現できます。リリース前に最低でも次の4つのシナリオを通しておくのがおすすめです。
第1シナリオは「子どもアカウントで購入リクエスト→親が承認→正常に権限付与」の正常系です。第2シナリオは「子どもアカウントで購入リクエスト→親が拒否」のキャンセル系で、.pending 状態の UI が正しく解除されるかを確認します。第3シナリオは「家族メンバーをファミリーから削除」の脱退系で、REVOKE がサーバーに飛び、該当メンバーの権限のみが取り消されることを確認します。第4シナリオは「親による Sandbox 払い戻し」で、Sandbox の管理画面から払い戻しを擬似発火させ、サーバーで REFUND を受信できるかを確認します。
これらの4シナリオを通せば、本番で発生しうる Family Sharing イベントの 80% 以上をカバーできます。残りの 20% は実機本番でのみ発生する稀なケース(ネットワーク断裂時のリトライ、Apple 側の遅延配信など)なので、リリース後のモニタリングで対応するのが現実的です。
リリース後にウォッチすべきメトリクス — エンタイトルメントのドリフトを早期発見する
Family Sharing 対応のアプリを本番リリースしたあと、私が必ず継続監視しているのは「Apple 上のアクティブ課金者数」と「自分のサーバー KV 上で active 状態のユーザー数」の差分です。実装が健全であれば、この2つの数字は1日単位で 1% 以内のずれに収まります。差分が数パーセントに広がっていたら、どこかの通知ハンドラが落ちているか、appAccountToken のマッピングがずれているかのどちらかです。
私は毎日深夜に App Store Server API からアクティブな購読者数を取得し、KV 上の active レコード数と突合するジョブを動かしています。差分は Slack チャネルに投稿され、もし日次の課金ユーザー数の 2% を超えていたら直接メンションが飛ぶように設定しました。差分の検知が翌月の集計まで遅れると、サポート問い合わせや返金リクエストとして表面化してから動くことになるので、日次のフィードバックループが結果的に一番安く済みます。
次に追っているのは、ownershipType 別の REVOKE イベント発生率です。Family Sharing 由来の REVOKE は、家族構成の変化が起きる頻度に比例して発生するので、本来は低くゆっくりとしたペースで推移します。これが急に跳ねていたら、たいていはクライアント UI のどこかに「家族から抜ける操作」を誤発火させるバグが入っています。修正は UX 改善(「本当によろしいですか? アクセスがすぐに失効します」を確認ダイアログに足す等)で済みますが、グラフを描いていなければ問題そのものに気づけません。
3つ目に追っているのは、.pending のまま解決しない購入リクエストの割合です。子どもが購入をリクエストしたまま、親が承認も拒否もしないケースです。この比率が 10% を超えたら、Ask to Buy の親への通知導線にフリクションがあるサインです。親のデバイスでプッシュ通知が届いていない、商品の説明文が iOS の承認シートに収まりきらず読まれていない、といった原因が多く、可視化することで初めて改善できます。
最後に追っているのは DID_FAIL_TO_RENEW の発生率です。月末締めのタイミングで一斉に発生する場合、家族メンバーのうち誰かのカードが切れたサインです。プロアクティブにアプリ内バナーで「家族プランの支払い方法に問題が発生しています」と表示し、iOS のサブスクリプション管理画面へディープリンクするだけで、低評価レビューを大幅に減らせます。
このダッシュボードを構築する作業はだいたい1日で済みます。Server Notifications V2 をすでに実装していれば、KV と Slack を組み合わせて差分を出すスクリプトを書くだけです。リリース後の課金数字に振り回されなくなることのリターンは、その手間に十分見合います。
本番リリース前のチェックリスト
ここまでの内容を踏まえて、本番リリース前に確認すべきポイントを整理します。
App Store Connect の対象商品で Family Sharing が 有効化されている ことを確認する
商品種別が「自動更新サブスクリプション」または「非消費型アイテム」であることを確認する(消費型・非自動更新は非対応)
StoreKit 2 のクライアント側で Transaction.updates をアプリ起動直後から購読する実装になっている
ownershipType == .familyShared のときに、解約フォームや支払い管理 UI を表示しない分岐が入っている
Product.purchase() の .pending 状態を、購入完了として扱わず「承認待ち」UI で待機させる
サーバー側で App Store Server Notifications V2 を受信し、REFUND REVOKE を受け取った瞬間に権限を取り消す
クライアントが REFUND 後に古いキャッシュを使い続けないよう、APNs サイレント Push か ETag で再検証する仕組みが入っている
Sandbox で前述の4シナリオ(正常承認・拒否・脱退・返金)を通している
App Store Server Notifications V2 のエンドポイントを Sandbox と本番で別々に設定している
子どもアカウントから購入された場合の、保護者向けレシート・領収メールが正しい言語で送られる設定になっている
私の場合、このチェックリストを家族向けアプリのリリース前に必ず通すようにしてから、Family Sharing 由来の問い合わせと低評価レビューが目に見えて減りました。リリース前1日で全項目を消化できる量なので、習慣として組み込むのがおすすめです。
StoreKit 2 のサブスクリプション周りをもっと深く理解したい場合は、Rork Max で StoreKit 2 の In-App Purchase を実装する — サブスクリプション課金の完全ガイド も合わせて参照すると、Family Sharing 対応の実装がより堅牢になります。RevenueCat を使う場合の基本実装は RorkアプリにRevenueCatでアプリ内課金・サブスクリプションを実装する完全ガイド で押さえられます。
次の一歩
まずは手元のアプリの App Store Connect 設定を開き、現在販売中のサブスクリプションが Family Sharing 対応になっているかを確認してみてください。もし対応していて、ownershipType を見ずに権限付与している実装が混じっているなら、今日のうちに .familyShared の分岐を入れるだけでも、家族メンバーに対する解約事故とサポート問い合わせを大幅に減らせます。リリース後に気づいて修正するより、今のうちに整えておく方が明確に楽です。
Family Sharing 対応の判断軸として「家族向けプランを売る戦略なのか、ソロプランを守る戦略なのか」を決める章があり、私自身も価格設計で迷ったときに何度か読み返しました。