2016-08-22 9 views
0

У меня есть приложение Xamarin iOS со встроенными приложениями. Я получаю base64 закодированное приложение квитанции:Распаковать приложение iOS в C# и php

NSUrl receiptURL = NSBundle.MainBundle.AppStoreReceiptUrl; 
String receiptData = receipt.GetBase64EncodedString(0); 

Согласно Apple, документы 7.0+ Квитанции приложения упаковывается в pkcs7 контейнере с использованием ASN1. Когда я отправляю его на сервер apple, он возвращает квитанцию ​​в JSON. Но я хочу разобрать его локально, чтобы узнать, что пользователь iaps уже имеет. Мне не нужна проверка, так как Apple делает это дистанционно, мне просто нужно получить квитанции для приобретенных inaps.

До сих пор, как следствие, я разбирал его в php с phpseclib вручную (не знаю, как это сделать программно), и получил его и проанализировал его.

$asn_parser = new File_ASN1(); 
//parse the receipt binary string 
$pkcs7 = $asn_parser->decodeBER(base64_decode($f)); 
//print_r($pkcs7); 
$payload_sequence = $pkcs7[0]['content'][1]['content'][0]['content'][2]['content']; 

$pld = $asn_parser->decodeBER($payload_sequence[1]['content'][0]['content']); 
//print_r($pld); 
$prd = $asn_parser->decodeBER($pld[0]['content'][21]['content'][2]['content']); 
print_r($prd); 

Но даже так у меня есть беспорядок атрибутов, каждый выглядит следующим образом:

         Array 
             (
              [start] => 271 
              [headerlength] => 2 
              [type] => 4 
              [content] => 2016-08-22T13:22:00Z 
              [length] => 24 
             ) 

Это не читаемый человек, мне нужно что-то вроде (выход с print_r из возвращаемого Apple) :

[receipt] => Array 
(
    [receipt_type] => ProductionSandbox 
    [adam_id] => 0 
    [app_item_id] => 0 
    [bundle_id] => com.my.test.app.iOS 
... 
    [in_app] => Array 
     (
      [0] => Array 
      (
       [quantity] => 1 
       [product_id] => test_iap_1 
       [transaction_id] => 1000000230806171 
... 
       [is_trial_period] => false 
      ) 
     ) 
) 

Все кажется слишком сложным, я с трудом верю, что распаковка чеков настолько сложна. Кто-нибудь знает, как справиться с этим? Я нашел this post, но библиотека написана в объективе-C, который не применим к моей текущей среде. Я бы сказал, что источники этой библиотеки меня пугают: столько сложного кода просто для распаковки стандартного контейнера. Я имею в виду, что работать с json, bson и т. Д. Очень просто, но не asn1.

+0

Квитанция не является отличным способом для управления запасами, так как расходные материалы исчезают из него (или, по крайней мере, это документально поведение, YMMV). Неиспользуемые материалы могут быть обнаружены, если пользователь выполняет восстановление. Что происходит, когда вам нужно знать, что находится в квитанции? –

+0

переустановленное приложение, обновленная приписка к приему - единственное место, где я могу увидеть, какой pruduct приобретен. Разумеется, ресурсы хранятся в локальных файлах и облаках. Во всяком случае, я распаковал его с помощью Liping Dai LCLib (lipingshare.com). Asn1Parser возвращает дерево типа DOM с корневым узлом - очень удобная библиотека. – Tertium

+0

Хорошая находка в библиотеке. Все еще немного запутано - в квитанции не должно быть никаких расходных материалов, и восстановление пользователя должно вернуть вам неиспользованные ресурсы, но если это сработает для вас, то, думаю, вы в порядке. –

ответ

0

Наконец-то я распаковал его, используя Liping Dai LCLib (lipingshare.com). Asn1Parser возвращает дерево типа DOM с корневым узлом - очень удобная библиотека.

public class AppleAppReceipt 
    { 
     public class AppleInAppPurchaseReceipt 
     { 
      public int Quantity; 
      public string ProductIdentifier; 
      public string TransactionIdentifier; 
      public DateTime PurchaseDate; 
      public string OriginalTransactionIdentifier; 
      public DateTime OriginalPurchaseDate; 
      public DateTime SubscriptionExpirationDate; 
      public DateTime CancellationDate; 
      public int WebOrderLineItemID; 
     } 

     const int AppReceiptASN1TypeBundleIdentifier = 2; 
     const int AppReceiptASN1TypeAppVersion = 3; 
     const int AppReceiptASN1TypeOpaqueValue = 4; 
     const int AppReceiptASN1TypeHash = 5; 
     const int AppReceiptASN1TypeReceiptCreationDate = 12; 
     const int AppReceiptASN1TypeInAppPurchaseReceipt = 17; 
     const int AppReceiptASN1TypeOriginalAppVersion = 19; 
     const int AppReceiptASN1TypeReceiptExpirationDate = 21; 

     const int AppReceiptASN1TypeQuantity = 1701; 
     const int AppReceiptASN1TypeProductIdentifier = 1702; 
     const int AppReceiptASN1TypeTransactionIdentifier = 1703; 
     const int AppReceiptASN1TypePurchaseDate = 1704; 
     const int AppReceiptASN1TypeOriginalTransactionIdentifier = 1705; 
     const int AppReceiptASN1TypeOriginalPurchaseDate = 1706; 
     const int AppReceiptASN1TypeSubscriptionExpirationDate = 1708; 
     const int AppReceiptASN1TypeWebOrderLineItemID = 1711; 
     const int AppReceiptASN1TypeCancellationDate = 1712; 

     public string BundleIdentifier; 
     public string AppVersion; 
     public string OriginalAppVersion; //какую покупали 
     public DateTime ReceiptCreationDate; 

     public Dictionary<string, AppleInAppPurchaseReceipt> PurchaseReceipts; 

     public bool parseAsn1Data(byte[] val) 
     { 
      if (val == null) 
       return false; 
      Asn1Parser p = new Asn1Parser(); 
      var stream = new MemoryStream(val); 
      try 
      { 
       p.LoadData(stream); 
      } 
      catch (Exception e) 
      { 
       return false; 
      } 

      Asn1Node root = p.RootNode; 
      if (root == null) 
       return false; 

      PurchaseReceipts = new Dictionary<string, AppleInAppPurchaseReceipt>(); 
      parseNodeRecursive(root); 

      return !string.IsNullOrEmpty(BundleIdentifier); 
     } 


     private static string getStringFromSubNode(Asn1Node nn) 
     { 
      string dataStr = null; 

      if ((nn.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.OCTET_STRING && nn.ChildNodeCount > 0) 
      { 
       Asn1Node n = nn.GetChildNode(0); 

       switch (n.Tag & Asn1Tag.TAG_MASK) 
       { 
        case Asn1Tag.PRINTABLE_STRING: 
        case Asn1Tag.IA5_STRING: 
        case Asn1Tag.UNIVERSAL_STRING: 
        case Asn1Tag.VISIBLE_STRING: 
        case Asn1Tag.NUMERIC_STRING: 
        case Asn1Tag.UTC_TIME: 
        case Asn1Tag.UTF8_STRING: 
        case Asn1Tag.BMPSTRING: 
        case Asn1Tag.GENERAL_STRING: 
        case Asn1Tag.GENERALIZED_TIME: 
         { 
          if ((n.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.UTF8_STRING) 
          { 
           UTF8Encoding unicode = new UTF8Encoding(); 
           dataStr = unicode.GetString(n.Data); 
          } 
          else 
          { 
           dataStr = Asn1Util.BytesToString(n.Data); 
          } 
         } 
         break; 
       } 
      } 
      return dataStr; 
     } 
     private static DateTime getDateTimeFromSubNode(Asn1Node nn) 
     { 
      string dataStr = getStringFromSubNode(nn); 
      if (string.IsNullOrEmpty(dataStr)) 
       return DateTime.MinValue; 
      DateTime retval = DateTime.MaxValue; 
      try 
      { 
       retval = DateTime.Parse(dataStr); 
      } 
      catch (Exception e) 
      { 
      } 
      return retval; 
     } 

     private static int getIntegerFromSubNode(Asn1Node nn) 
     { 
      int retval = -1; 

      if ((nn.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.OCTET_STRING && nn.ChildNodeCount > 0) 
      { 
       Asn1Node n = nn.GetChildNode(0); 
       if ((n.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.INTEGER) 
        retval = (int)Asn1Util.BytesToLong(n.Data); 
      } 
      return retval; 
     } 

     private void parseNodeRecursive(Asn1Node tNode) 
     { 
      bool processed_node = false; 
      if ((tNode.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.SEQUENCE && tNode.ChildNodeCount == 3) 
      { 
       Asn1Node node1 = tNode.GetChildNode(0); 
       Asn1Node node2 = tNode.GetChildNode(1); 
       Asn1Node node3 = tNode.GetChildNode(2); 

       if ((node1.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.INTEGER && (node2.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.INTEGER && 
        (node3.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.OCTET_STRING) 
       { 
        processed_node = true; 
        int type = (int)Asn1Util.BytesToLong(node1.Data); 
        switch (type) 
        { 
         case AppReceiptASN1TypeBundleIdentifier: 
          BundleIdentifier = getStringFromSubNode(node3); 
          break; 
         case AppReceiptASN1TypeAppVersion: 
          AppVersion = getStringFromSubNode(node3); 
          break; 
         case AppReceiptASN1TypeOpaqueValue: 
          break; 
         case AppReceiptASN1TypeHash: 
          break; 
         case AppReceiptASN1TypeOriginalAppVersion: 
          OriginalAppVersion = getStringFromSubNode(node3); 
          break; 
         case AppReceiptASN1TypeReceiptExpirationDate: 
          break; 
         case AppReceiptASN1TypeReceiptCreationDate: 
          ReceiptCreationDate = getDateTimeFromSubNode(node3); 
          break; 
         case AppReceiptASN1TypeInAppPurchaseReceipt: 
          { 
           if (node3.ChildNodeCount > 0) 
           { 
            Asn1Node node31 = node3.GetChildNode(0); 
            if ((node31.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.SET && node31.ChildNodeCount > 0) 
            { 
             AppleInAppPurchaseReceipt receipt = new AppleInAppPurchaseReceipt(); 

             for (int i = 0; i < node31.ChildNodeCount; i++) 
             { 
              Asn1Node node311 = node31.GetChildNode(i); 
              if ((node311.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.SEQUENCE && node311.ChildNodeCount == 3) 
              { 
               Asn1Node node3111 = node311.GetChildNode(0); 
               Asn1Node node3112 = node311.GetChildNode(1); 
               Asn1Node node3113 = node311.GetChildNode(2); 
               if ((node3111.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.INTEGER && (node3112.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.INTEGER && 
                (node3113.Tag & Asn1Tag.TAG_MASK) == Asn1Tag.OCTET_STRING) 
               { 
                int type1 = (int)Asn1Util.BytesToLong(node3111.Data); 
                switch (type1) 
                { 
                 case AppReceiptASN1TypeQuantity: 
                  receipt.Quantity = getIntegerFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeProductIdentifier: 
                  receipt.ProductIdentifier = getStringFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeTransactionIdentifier: 
                  receipt.TransactionIdentifier = getStringFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypePurchaseDate: 
                  receipt.PurchaseDate = getDateTimeFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeOriginalTransactionIdentifier: 
                  receipt.OriginalTransactionIdentifier = getStringFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeOriginalPurchaseDate: 
                  receipt.OriginalPurchaseDate = getDateTimeFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeSubscriptionExpirationDate: 
                  receipt.SubscriptionExpirationDate = getDateTimeFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeWebOrderLineItemID: 
                  receipt.WebOrderLineItemID = getIntegerFromSubNode(node3113); 
                  break; 
                 case AppReceiptASN1TypeCancellationDate: 
                  receipt.CancellationDate = getDateTimeFromSubNode(node3113); 
                  break; 
                } 
               } 
              } 
             } 

             if (!string.IsNullOrEmpty(receipt.ProductIdentifier)) 
              PurchaseReceipts.Add(receipt.ProductIdentifier, receipt); 
            } 
           } 
          } 
          break; 
         default: 
          processed_node = false; 
          break; 
        } 
       } 
      } 


      if (!processed_node) 
      { 
       for (int i = 0; i < tNode.ChildNodeCount; i++) 
       { 
        Asn1Node chld = tNode.GetChildNode(i); 
        if (chld != null) 
         parseNodeRecursive(chld); 
       } 
      } 
     } 
    } 

И использование:

public void printAppReceipt() 
    { 
     NSUrl receiptURL = NSBundle.MainBundle.AppStoreReceiptUrl; 
     if (receiptURL != null) 
     { 
      Console.WriteLine("receiptUrl='" + receiptURL + "'"); 

      NSData receipt = NSData.FromUrl(receiptURL); 
      if (receipt != null) 
      { 
       byte[] rbytes = receipt.ToArray(); 
       AppleAppReceipt apprec = new AppleAppReceipt(); 
       if (apprec.parseAsn1Data(rbytes)) 
       { 
        Console.WriteLine("Received receipt for " + apprec.BundleIdentifier + " with " + apprec.PurchaseReceipts.Count + 
             " products"); 
        Console.WriteLine(JsonConvert.SerializeObject(apprec,Formatting.Indented)); 
       } 
      } 
      else 
       Console.WriteLine("receipt == null"); 
     } 
    }