React-native-iap: Проверка получения iOS со стороны сервера

Созданный на 28 янв. 2020  ·  9Комментарии  ·  Источник: dooboolab/react-native-iap

Я использую свой внутренний сервер для проверки квитанции iOS. Для этого все, что я сделал, - это отправил файл Purchase.transactionReceipt серверу приложений в почтовом запросе. Я хочу знать, возвращает ли покупка.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 Не могли бы вы помочь здесь, пожалуйста. Квитанция о транзакции, которую я получаю ... это строка в кодировке base 64? Если нет, то как его преобразовать. Я отправляю это на свой сервер BE, откуда я нажимаю API проверки квитанции Apple.

Я сейчас занимаюсь той же проблемой. На ios transactionReceipt «выглядит» как строка base64, но это не так. Если я декодирую его на устройстве и выводю в консоль, я получаю экраны мусора со случайными случайными словами.

Затем я попытался отправить его, как и мой сервер BE. Там я декодировал его с помощью ядра dotnet, используя этот код:

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://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

@leelandclay :
«Мне удалось получить полезную информацию, передав необработанную строку на мой сервер, а затем отправив ее в конечную точку verifyReceipt».
Вы только что отправили эту строку transactionReceipt в BE?
Я просмотрел документацию Apple, но там метод создания данных квитанции предназначен для собственного приложения iOS, в котором мы получаем appStoreReceiptURL.

да. Передайте объект, который вы получили из iap, на сервер, затем отправьте его на веб-сайт, и вы получите объект JSON.

Хорошо ... Вот где я сейчас ...

Это код, который у меня есть в моем приложении:

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 я просто создаю следующий объект и отправляю его POST в конечную точку / verifyReceipt (используя расположение серверов в указанной выше документации).

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

Я могу получить ответы с достоверной информацией. Часть ответа - это запись «latest_receipt» (используется позже). Я сохраняю все в своей базе данных, а также передаю информацию обратно в приложение, чтобы оно могло вызвать finishTransaction и сохранить действительную квитанцию ​​в состоянии, которое будет использоваться.

Я также работаю над заданием cron, которое будет выполняться один раз в день, чтобы захватить все подписки, которые отображаются как активные и прошли дату истечения срока действия. Это соответствует тому же процессу, что и отправка информации в verifyReceipt. "Данные-квитанции", которые я передаю, - это "latest_receipt", который я сохранил из предыдущего вызова verifyReceipt.

По-видимому, есть какой-то странный способ, которым песочница обрабатывает автоматически возобновляемые подписки, поэтому я жду ответа на форумах разработчиков Apple о том, что мне действительно нужно искать.

Ситуация, которую я вижу, заключается в том, что после покупки подписки я получаю ответ, который показывает автоматическое продление как истинное (pending_renewal_info [0] .auto_renew_status из verifyReceipt отображается как 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 рейтинги