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你能在这里

我现在正在研究同样的问题。 在 ios 上,transactionReceipt“看起来”像一个 base64 字符串,但它不是。 如果我在设备上对其进行解码并输出到控制台,我会看到带有偶尔随机单词的垃圾屏幕。

然后我尝试发送它,就像我的 BE 服务器一样。 在那里,我使用 dotnet core 使用以下代码对其进行了解码:

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 :

@李兰克莱
“通过将原始字符串传递到我的服务器,然后将其发布到 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 服务器上,我只需创建以下对象并将其发布到 /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 而言,这遵循相同的过程。 我传入的“receipt-data”是我从之前的 verifyReceipt 调用中存储的“latest_receipt”。

显然,沙箱处理自动续订订阅的方式有些奇怪,所以我正在等待 Apple Developer Forums 上关于我真正应该寻找的内容的回复。

我看到的情况是,购买订阅后,我收到一个响应,显示自动续订为真(来自 verifyReceipt 的 pending_renewal_info[0].auto_renew_status 显示为 1),并且 expires_date 条目(有 3 个)显示它在未来(5 分钟)。 如果我等到过期时间后再次调用 verifyReceipt,它将显示 expires_date 和自动更新的完全相同的值。

我的计划是使用自动续订 === false 和 expires time < 当前时间的条件来确定是否停用订阅。 有没有其他人看到过这个来自 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 等级