Validating appReceiptStoreURL Returning 21002 Status

故事扮演 提交于 2019-12-05 14:12:38

Comparing your PHP with mine (which I know works) is difficult because I am using HTTPRequest rather than the raw curl APIs. However, it seems to me that you are setting the "{receipt-data:..}" JSON string as merely a field in the POST data rather than as the raw POST data itself, which is what my code is doing.

curl_setopt($curlHandle, CURLOPT_POST, true);
curl_setopt($curlHandle, CURLOPT_POSTFIELDS, $postData); // Possible problem
$encodedResponse = curl_exec($curlHandle);

Compared to:

$postData = '{"receipt-data" : "'.$receipt.'"}'; // yay one-off JSON serialization!
$request = new HTTPRequest('https://sandbox.itunes.apple.com/verifyReceipt', HTTP_METH_POST);
$request->setBody($postData); // Relevant difference...
$request->send();
$encodedResponse = $request->getResponseBody();

I have changed my variable names a bit to make them match up with your example.

the code 21002 means "The data in the receipt-data property was malformed or missing."

you can find it in https://developer.apple.com/library/archive/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html

below code is my class for appstore in-app verifyRecepip, GuzzleHttp is required, you can install it by composer require guzzlehttp/guzzle https://github.com/guzzle/guzzle

<?php

namespace App\Libraries;

class AppStoreIAP
{
  const SANDBOX_URL    = 'https://sandbox.itunes.apple.com/verifyReceipt';
  const PRODUCTION_URL = 'https://buy.itunes.apple.com/verifyReceipt';

  protected $receipt = null;

  protected $receiptData = null;

  protected $endpoint = 'production';

  public function __construct($receipt, $endpoint = self::PRODUCTION_URL)
  {
      $this->receipt  = json_encode(['receipt-data' => $receipt]);
      $this->endpoint = $endpoint;
  }


  public function setEndPoint($endpoint)
  {
      $this->endpoint = $endpoint;
  }


  public function getReceipt()
  {
      return $this->receipt;
  }


  public function getReceiptData()
  {
      return $this->receiptData;
  }


  public function getEndpoint()
  {
      return $this->endpoint;
  }


  public function validate($bundle_id, $transaction_id, $product_code)
  {
      $http = new \GuzzleHttp\Client([
          'headers' => [
              'Content-Type' => 'application/x-www-form-urlencoded',
          ],
          'timeout' => 4.0,
      ]);
      $res               = $http->request('POST', $this->endpoint, ['body' => $this->receipt]);
      $receiptData       = json_decode((string) $res->getBody(), true);
      $this->receiptData = $receiptData;
      switch ($receiptData['status']) {
          case 0: // verify Ok
              // check bundle_id
              if (!empty($receiptData['receipt']['bundle_id'])) {
                  $receipt_bundle_id = $receiptData['receipt']['bundle_id'];
                  if ($receipt_bundle_id != $bundle_id) {
                      throw new \Exception('bundle_id not matched!');
                  }
              }
              // check transaction_id , product_id
              if (!empty($receiptData['receipt']['in_app'])) {
                  $in_app = array_combine(array_column($receiptData['receipt']['in_app'], 'transaction_id'), $receiptData['receipt']['in_app']);
                  if (empty($in_app[$transaction_id])) {
                      throw new \Exception('transaction_id is empty!');
                  }
                  $data = $in_app[$transaction_id];
                  if ($data['product_id'] != $product_code) {
                      throw new \Exception('product_id not matched!');
                  }
              } else {
                  $receipt_transaction_id = $receiptData['receipt']['transaction_id'];
                  $receipt_product_id     = $receiptData['receipt']['product_id'];
                  if ($receipt_transaction_id != $transaction_id || $product_id != $product_code) {
                      throw new \Exception('tranaction_id not matched!');
                  }
              }
              break;
          case 21007:// sandbox order validate in production will return 21007
              if ($this->getEndpoint() != self::SANDBOX_URL) {
                  $this->setEndPoint(self::SANDBOX_URL);
                  $this->validate($bundle_id, $transaction_id, $product_code);
              } else {
                  throw new \Exception('appstore error!');
              }
              break;
          default:
              throw new \Exception("[{$receiptData['status']}]appstore error!");
              break;
      }
      return $receiptData;
  }
}
Joyce H

I think Morteza M is correct. I did a test and got reply(JSON) like:

{
'status':
'environment': 'Sandbox'
  'receipt':
  {
    'download_id':
    ....
    'in_app":
    {
      'product_id':
      ....
    }
    ....
  }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!