React-native-iap: サーバー側からのiOSレシート検証

作成日 2020年01月28日  ·  9コメント  ·  ソース: dooboolab/react-native-iap

バックエンドサーバーを使用してiOSレシートを検証しています。 そのために私が行ったのは、postリクエストでpurchase.transactionReceiptをアプリケーションサーバーに渡すことだけです。 Purchase.transactionReceiptがbase64でエンコードされたレシートデータを返すかどうかを知りたいのですが、何ですか? エラーが発生します:現在の実装では21002

requestServerForReceiptVerification = (purchase: InAppPurchase | SubscriptionPurchase) => {
        const receipt = purchase.transactionReceipt;
        if (receipt) {
            // Hit Server API for Receipt Validation
            if (Platform.OS == 'ios') {
                let headers = {
                    'Content-Type': 'application/json'
                }
                Stores.UserStore.hitVerifyiOSReceiptAPI(receipt, headers, this.dropdown, (response: any) => {
                    finishTransaction(purchase).then(() => {
                        console.warn('Trasaction Finished');
                    }).catch((error) => {
                        console.warn(error.message);
                    })
                })
            } 
        }
    }
📱 iOS 🕵️‍♂️ need more investigation 🚶🏻 stale

最も参考になるコメント

これをチェックしてください

const IOS_SHARED_PWD = "********";

async function validateIOS() {
    const latestPurchase = await getLatestPurchase();
    if (latestPurchase) {
        return false;
    }

    return RNIap.validateReceiptIos(
        {
            "receipt-data": latestPurchase.transactionReceipt,
            "password": IOS_SHARED_PWD,
            "exclude-old-transactions": false
        },
        false
    )
        .then(uncheckedValidation => {
            //check test receipt
            if (uncheckedValidation?.status === 21007) {
                return RNIap.validateReceiptIos(
                    {
                        "receipt-data": latestPurchase.transactionReceipt,
                        "password": IOS_SHARED_PWD,
                        "exclude-old-transactions": false
                    },
                    true
                );
            } else {
                return uncheckedValidation;
            }
        })
        .then(checkedValidation => {
            return isValidReceipt(checkedValidation);
        });
}

function getLatestPurchase() {
    return RNIap.getAvailablePurchases().then(purchases => {
        return purchases?.sort((a, b) => Number(b.transactionDate) - Number(a.transactionDate))?.[0] || null;
    });
}

function isValidReceipt(checkedValidation) {
    if (!checkedValidation) {
        return false;
    }

    // check is valid validation request
    if (checkedValidation.status === 21006 || checkedValidation.status === 21010) {
        return false;
    }

    const { latest_receipt_info: latestReceiptInfo } = checkedValidation;
    const latestReceipt = latestReceiptInfo
        ?.sort((a, b) => Number(b.purchase_date_ms) - Number(a.purchase_date_ms))
        ?.find(receipt => receipt.product_id === "some.product.id")?.[0];

    // no receipt
    if (!latestReceipt) {
        return false;
    }

    // refunded receipt
    if (latestReceipt.cancellation_date) {
        return false;
    }

    // expired receipt
    if (Number(latestReceipt.expires_date_ms) < Date.now()) {
        return false;
    }

    return checkedValidation.status === 0;
}

全てのコメント9件

@hyochanここで手伝ってくれませんか。 私が受け取っているトランザクションレシート..それはbase64でエンコードされた文字列ですか? そうでない場合は、どうすれば変換できますか。 これをBEサーバーに送信し、そこからAppleレシート検証APIを実行します。

私は今、これと同じ問題を調査しています。 iOSでは、transactionReceiptはbase64文字列のように「見えます」が、そうではありません。 デバイスでデコードしてコンソールに出力すると、ランダムな単語がときどき表示されるゴミの画面が表示されます。

次に、BEサーバーまでのように送信しようとしました。 そこで、次のコードを使用してdotnetcoreを使用してデコードしました。

string base64Decoded;
byte[] data = System.Convert.FromBase64String(receiptString);
base64Decoded = System.Text.Encoding.ASCII.GetString(data);
Console.WriteLine("ios receiptString:");
Console.WriteLine(base64Decoded);

そのための出力は次のとおりです。
Screen Shot 2020-02-07 at 10 58 37 AM

私の考えでは、領収書には各エントリがエンコードされていました...その後、領収書全体がエンコードされました。 私はその理論をテストし始める前にここで答えを見つけることを望んでいました...しかし私は先に進んでそれをするつもりです。

私は少し進歩しました。 デコード可能なものを返すvalidateReceiptIos関数を取得できませんでした。 それについての私の考えは、Appleがそれをエンコードする方法に変更があったということです。

生の文字列をサーバーに渡し、verifyReceiptエンドポイントに投稿することで、使用可能な情報を取得できました。 私はこれをガイドとして使用しました: https

@leelandclay
「生の文字列をサーバーに渡して、verifyReceiptエンドポイントに投稿することで、使用可能な情報を取得できました。」
そのtransactionReceipt文字列をBEに送信しましたか?
Appleのドキュメントを確認しましたが、レシートデータを作成する方法は、appStoreReceiptURLを取得するネイティブiOSアプリケーション用です。

はい。 iapから取得したオブジェクトをサーバーに渡し、それをWebサイトに送信すると、JSONオブジェクトが返されます。

OK ...これが私が今いるところです...

これは私のアプリ内にあるコードです:

this.purchaseUpdateSubscription = purchaseUpdatedListener(
  (purchase: InAppPurchase | SubscriptionPurchase) => {
    console.log('purchaseUpdateSubscription called');
    if (purchase) {
      try {
        sendTransactionReceipt(this.state.profile.userId, purchase.transactionReceipt, this.state.token)
          .then(transactionReceipt => {
            finishTransaction(purchase, false)
              .then(finish => {
                this.setState({ transactionReceipt }, () => { this.sendingTransactionReceipt = false; });
              })
              .catch(err => {
                console.log('FinishTransaction ERROR: ' + err);
                this.sendingTransactionReceipt = false;
              });
          })
          .catch(() => {
            this.sendingTransactionReceipt = false;
          });
      } catch (ackErr) {
        console.warn('ackErr', ackErr);
      }
    }
  },
);

BEサーバーで、次のオブジェクトを作成し、それを/ verifyReceiptエンドポイントにPOSTします(上記のドキュメントのサーバーの場所を使用)。

{
  "receipt-data": <purchase.transactionReceipt received from app>,
  "password": <secret password from within your apple account>,
  "exclude-old-transactions": true
}

有効な情報で回答を返すことができます。 応答の一部は「latest_receipt」エントリです(後で使用されます)。 私はすべてをデータベースに保存し、情報をアプリに返して、finishTransactionを呼び出し、有効なレシートを使用する状態で保存できるようにします。

また、アクティブとして表示され、有効期限を過ぎたすべてのサブスクリプションを取得するために1日1回実行されるcronジョブにも取り組んでいます。 これは、verifyReceiptに情報を送信する限り同じプロセスに従います。 渡す「receipt-data」は、前回のverifyReceipt呼び出しで保存した「latest_receipt」です。

どうやら、サンドボックスが自動更新サブスクリプションを処理する奇妙な方法があるので、私は実際に何を探すべきかについてApple DeveloperForumsで応答を得るのを待っています。

私が見ている状況は、サブスクリプションを購入した後、自動更新がtrueとして表示される応答を受け取り(verifyReceiptからのpending_renewal_info [0] .auto_renew_statusは1として表示されます)、expires_dateエントリ(3つあります)はそれが未来(5分)。 有効期限が切れるまで待ってからもう一度verifyReceiptを呼び出すと、expires_dateと自動更新にまったく同じ値が表示されます。

私の計画は、自動更新=== falseの条件を使用し、有効期限<現在の時刻を使用して、サブスクリプションを非アクティブ化するかどうかを決定することでした。 他の誰かがverifyReceiptからのこの応答を見たことがありますか????

これをチェックしてください

const IOS_SHARED_PWD = "********";

async function validateIOS() {
    const latestPurchase = await getLatestPurchase();
    if (latestPurchase) {
        return false;
    }

    return RNIap.validateReceiptIos(
        {
            "receipt-data": latestPurchase.transactionReceipt,
            "password": IOS_SHARED_PWD,
            "exclude-old-transactions": false
        },
        false
    )
        .then(uncheckedValidation => {
            //check test receipt
            if (uncheckedValidation?.status === 21007) {
                return RNIap.validateReceiptIos(
                    {
                        "receipt-data": latestPurchase.transactionReceipt,
                        "password": IOS_SHARED_PWD,
                        "exclude-old-transactions": false
                    },
                    true
                );
            } else {
                return uncheckedValidation;
            }
        })
        .then(checkedValidation => {
            return isValidReceipt(checkedValidation);
        });
}

function getLatestPurchase() {
    return RNIap.getAvailablePurchases().then(purchases => {
        return purchases?.sort((a, b) => Number(b.transactionDate) - Number(a.transactionDate))?.[0] || null;
    });
}

function isValidReceipt(checkedValidation) {
    if (!checkedValidation) {
        return false;
    }

    // check is valid validation request
    if (checkedValidation.status === 21006 || checkedValidation.status === 21010) {
        return false;
    }

    const { latest_receipt_info: latestReceiptInfo } = checkedValidation;
    const latestReceipt = latestReceiptInfo
        ?.sort((a, b) => Number(b.purchase_date_ms) - Number(a.purchase_date_ms))
        ?.find(receipt => receipt.product_id === "some.product.id")?.[0];

    // no receipt
    if (!latestReceipt) {
        return false;
    }

    // refunded receipt
    if (latestReceipt.cancellation_date) {
        return false;
    }

    // expired receipt
    if (Number(latestReceipt.expires_date_ms) < Date.now()) {
        return false;
    }

    return checkedValidation.status === 0;
}

ねえ、最近この問題に関する活動はなかったようです。 問題は修正されましたか、それともコミュニティの注意が必要ですか? それ以上のアクティビティが発生しない場合、この問題は解決される可能性があります。 この問題に「ディスカッション用」または「良い最初の問題」というラベルを付けることもできます。開いたままにしておきます。 貢献していただきありがとうございます。

@leelandclayソリューションを提供していただきありがとうございます。 これは本当に大いに役立ち、最終的に領収書を検証することができました。 ありがとう !!

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