React-native-iap: PurchaseUpdatedListenerは、購入後も複数回呼び出されます

作成日 2020年10月26日  ·  22コメント  ·  ソース: dooboolab/react-native-iap

react-native-iapのバージョン

5.0.1

react-nativeのバージョン

0.63.3

エラーに直面したプラットフォーム(IOSまたはAndroid、あるいはその両方?)

IOS

予想される行動

PurchaseUpdatedListenerが複数回呼び出されない

実際の動作

PurchaseUpdatedListenerが起動時に複数回、場合によってはその間に呼び出される

テストされた環境(エミュレーター?実デバイス?)

実際のデバイス(テストフライトおよびAppStoreのiOS 14、13)

動作を再現する手順

(ナビゲーターコンポーネント)

const itemSkus = ['01', '02'];

const processNewPurchase = async (purchase) => {
  const { productId, transactionReceipt } = purchase;
  if (transactionReceipt !== undefined && transactionReceipt) {
       //backend call with fetch - validating receipt 
        if (data.ack === 'success') {
          console.log('finished');
          await finishTransaction(purchase);
      } else if (data.ack === 'failure') {
          props.setProcessing(false);
          console.log('error');
      }
    }
};


const getProductsIAP = useCallback(async () => {
  await clearProductsIOS();
  await clearTransactionIOS();

 try {
   const result = await initConnection();
   const products = await getProducts(itemSkus);
   props.setProducts(products);
   console.log('result', result);
 } catch (err) {
   console.warn(err.code, err.message);
 }

  purchaseUpdateSubscription = purchaseUpdatedListener(
    async (purchase) => {
      const receipt = purchase.transactionReceipt;
      console.log('purchaseUpdatedListener');
      if (receipt) {
        try {
          await processNewPurchase(purchase);
        } catch (ackErr) {
           console.log('ackErr', ackErr);
        }
      } else {
        console.log('purchaseUpdatedListener error: receipt');
      }
    },
  );

  purchaseErrorSubscription = purchaseErrorListener(
    (error: PurchaseError) => {
      console.log('purchaseErrorListener', error);
      console.log(JSON.stringify(error));
    },
  );

  setLoading(false);
}, []);


useEffect(() => {
  getProductsIAP();

  return () => {
    if (purchaseUpdateSubscription) {
      purchaseUpdateSubscription.remove();
      purchaseUpdateSubscription = null;
    }
    if (purchaseErrorSubscription) {
      purchaseErrorSubscription.remove();
      purchaseErrorSubscription = null;
    }
  };
}, []);

📱 iOS 🙏 help wanted

最も参考になるコメント

それでも同じ問題。 PurchaseUpdatedListenerが壊れているようです。 この問題をできるだけ早く修正してください。 解決策が見つかりません。

全てのコメント22件

イベントは同じ購入結果を発生させますか?

はい、成功した結果/受信で起動します。 現在のレシートがない場合も起動しますが、検証サーバーでは失敗します。

コンポーネントが再レンダリングされていないかどうかを確認してください。 コンポーネントが再レンダリングされ、リスナーがときどき数回起動したときに、多くの問題が発生しました。 それは難しいリリースになるはずですが、私たちはあなたのケースを再現する必要があります。

「NavigatorContainer」とは、さまざまな状態を設定したり、更新したりすることを意味します。 だが 1つだけをレンダリングしてから、さまざまな状態、小道具を更新するために「レンダリング」します。

しかし、それが目的です-リスナーの購読を解除する/これを回避するためです。

useEffect(() => {
  getProductsIAP();

  return () => {
    if (purchaseUpdateSubscription) {
      purchaseUpdateSubscription.remove();
      purchaseUpdateSubscription = null;
    }
    if (purchaseErrorSubscription) {
      purchaseErrorSubscription.remove();
      purchaseErrorSubscription = null;
    }
  };
}, []);

「NavigatorContainer」とは、さまざまな状態を設定したり、更新したりすることを意味します。 ただし、レンダリングは1つだけで、その後、さまざまな状態や小道具を更新するために「レンダリング」します。

しかし、それが目的です-リスナーの購読を解除する/これを回避するためです。

useEffect(() => {
  getProductsIAP();

  return () => {
    if (purchaseUpdateSubscription) {
      purchaseUpdateSubscription.remove();
      purchaseUpdateSubscription = null;
    }
    if (purchaseErrorSubscription) {
      purchaseErrorSubscription.remove();
      purchaseErrorSubscription = null;
    }
  };
}, []);

はい、私もこれについて言及しました。 私が覚えている前の問題を共有しました。 複製が必要です。

また、トランザクションをクリアするか、別のtestflightアカウントで試すと役立つ場合があります。

const getProductsIAP = useCallback(async () => {
  await clearProductsIOS();
  await clearTransactionIOS();

.....

TestFlight、本番環境、およびさまざまなデバイスでテスト済み。

別のアカウント?

はい、同じ結果です。

奇妙な。 IapExampleプロジェクトのコードスニペットも試してみませんか?

私は次のような行動をします。

1)ユーザーがIAPを購入します。
2)purchaseUpdatedListenerが呼び出されます。
3)ユーザーがアプリをアンインストールします。
4)ユーザーがアプリに戻ります。
5)purchaseUpdatedListenerがトリガーされ、ループに入ります。

クラスを使用していますが、再レンダリングが表示されません。

私は「react-native-iap」:「4.4.1」と「react-native」:「0.63.3」を使用しています。

5.0.0にアップグレードしようとしましたが、同じ動作をします。

私は次のような行動をします。

  1. ユーザーがIAPを購入します。
  2. PurchaseUpdatedListenerが呼び出されます。
  3. ユーザーがアプリケーションをアンインストールします。
  4. ユーザーはアプリケーションに戻ります。
  5. PurchaseUpdatedListenerが起動し、ループを形成します。

クラスを使用していますが、繰り返しが表示されません。

私は「react-native-iap」:「4.4.1」と「react-native」:「0.63.3」にいます。

5.0.0にアップデートしようとしましたが、同じ動作がします。

こんにちは1つの質問。 これに対する解決策を見つけましたか?

それでも同じ問題。 PurchaseUpdatedListenerが壊れているようです。 この問題をできるだけ早く修正してください。 解決策が見つかりません。

これと同じ問題があります。 何ヶ月も修正されない可能性はありますか? これは重大な問題ではありませんか、それとも誰かがこれの回避策を見つけましたか? また、それが役立つ場合は、デバイスをiOS13からiOS14にアップグレードしたときにのみこの問題が発生したと思います。覚えている限り、iOS 13では正常に機能していましたが、誤解される可能性がありました。

ここで同じ問題。
コードを保存するたびに発生し、アプリがホットリロードされることに気付きました。
このシナリオでは、ルートコンポーネントがマウント解除されてから、再マウントされます。

これは私のuseIap()フックです

const useIap=()=>{

const purchaseUpdateSubscription = useRef<EmitterSubscription[]>([]);
    const purchaseErrorSubscription = useRef<EmitterSubscription[]>([]);

const init = useCallback(async () => {
        console.taggedLog(logTag, 'call iap init');

        await RNIap.initConnection();

        await RNIap.flushFailedPurchasesCachedAsPendingAndroid();

        purchaseUpdateSubscription.current.push(RNIap.purchaseUpdatedListener(async purchase => {
            console.taggedLog(logTag, 'purchaseUpdatedListener', purchase);

            try {
                await finalizePurchase(purchase);
            } catch (err) {
                handleFailedPurchase();
            }
        }));

        // use array to be sure to not accidentally overwrite subscriptions and lose access to them.
        // being an array I can always iterate and call `remove` on them
        purchaseErrorSubscription.current.push(RNIap.purchaseErrorListener(error => {
            handleFailedPurchase(error);
        }));

        updateProducts();
        return clear;
    }, []);

  const clear = useCallback(() => {
        console.taggedLog(logTag, 'clearing all');
        clearPurchaseEventListeners();
    }, [clearPurchaseEventListeners]);


 const clearPurchaseEventListeners = useCallback(() => {
        console.taggedLog(logTag, 'clearing purchase event listener', purchaseUpdateSubscription.current.length, purchaseErrorSubscription.current.length);
        purchaseUpdateSubscription.current.forEach(sub => sub.remove());
        purchaseErrorSubscription.current.forEach(sub => sub.remove());
        purchaseUpdateSubscription.current = [];
        purchaseErrorSubscription.current = [];
    }, []);

return {init,clear}
}

そして、ルートコンポーネントでこのコードを使用します

const iap=useIap();
 useEffect(() => {
        console.log("root component mounted");
        iap.init();

        return () => {
            console.log("root component unmounted");
            iap.clear();
        };
    }, []);

ルートコンポーネントがマウント解除および再マウントされる可能性がありますが、 clearメソッドが呼び出されます。また、ログ「購入イベントリスナーのクリア」1,1も表示されます。これは、それらをクリアすることを意味します。

この状況でも、購入のpurchaseUpdatedListenerは何度も呼び出されます。

解決策はありますか?

同じ問題があります。
それは別の画面でさえ呼び出されました。
これに関する更新はありますか?

私にとっても同じ問題です。 次回iapが初期化されるときに、過去のレシートを呼び出します。

私は同じ問題を抱えていましたが、コンポーネントを正しく組み立てたり分解したりしていないために同じことが起こっていることに気付き、同時に複数のリスナーを開いていました。

componentWillUnmount() { if (this.purchaseUpdateSubscription) { this.purchaseUpdateSubscription.remove(); this.purchaseUpdateSubscription = null; } }
購入してコンポーネントを分解するたびに、リスナー/サブスクリプションを削除し、コンポーネントを組み立てるときに新しいものを追加する必要があります。
このエラーはAndroidでは発生しません。なぜですか? わかりませんが、Androidで動作するため、エラーはiOSにあり、コードにはないと思われます。

考えられる回避策、テスト過去のレシート(サーバーに送信されたもの)が表示されることがあるので、受信した各通知のtransactionIdをすぐに保存するので、このtransactionIdが処理されているかどうか、またしたがって、無視します(重複しています)。そうでない場合は、続行できます。 通知が(サーバーではなく)アプリに最初に届く場合は、これを制御するロジックを配置できます。つまり、特定のボタンがクリックされた後にのみ1を受信する必要があります。
徹底的にテストした後、しばらくすると複製が停止し、そこで何かが起こっています。

はい、いくつかの大ざっぱな回避策がありますが、それらはすべてき​​れいです...。
この問題を修正してください。 これは間違いなく、2020年10月以降の重大でプロジェクトを壊すバグです。

唯一の解決策は、使用することです:Revenuecat;)

解決策はありますか?

Revenuecat

このページは役に立ちましたか?
0 / 5 - 0 評価