Caleb Pitman
Caleb Pitman

Reputation: 1165

Cordova In-App purchase Fovea plugin returning only id on IOS

When using the Cordova in app purchase plugin by fovea (https://github.com/j3k0/cordova-plugin-purchase/), the transaction object only contains an "id".

Why isn't it returning the rest of the details like Android?

I tested this on TestFlight with a sandbox user on a physical iPhone 5 with latest updates.

iOS response:

{  
   "id":"subscription_1",
   "alias":"Subscription 20",
   "type":"paid subscription",
   "state":"approved",
   "title":"Subscription",
   "description":"Subscription",
   "price":"$19.99",
   "currency":null,
   "loaded":true,
   "canPurchase":false,
   "owned":false,
   "downloading":false,
   "downloaded":false,
   "transaction":{  
      "type":"ios-appstore",
      "id":"1000000200491361"
   },
   "valid":true,
   "transactions":[  
      "1000000200450592",
      "1000000200450626",
      "1000000200450699",
      "1000000200450768",
      "1000000200450968",
      "1000000200451015",
      "1000000200451978",
      "1000000200452019",
      "1000000200452040",
      "1000000200452082",
      "1000000200452130",
      "1000000200452155",
      "1000000200471605",
      "1000000200471725",
      "1000000200471771",
      "1000000200491361"
   ]
}

Android Response:

{  
   "id":"iap_id",
   "alias":"Subscription 20",
   "type":"paid subscription",
   "state":"approved",
   "title":"Standard Subscription",
   "description":"Standard Subscription",
   "price":"$19.99",
   "currency":"USD",
   "loaded":true,
   "canPurchase":false,
   "owned":false,
   "downloading":false,
   "downloaded":false,
   "transaction":{  
      "type":"android-playstore",
      "purchaseToken":"bhgenijimhhgenhadngmajnp.AO-J1OxzqrUBfYXMJinFFjbRSUhL6E7bcbfnp0uZpEWi_ziPiimWbFt4n7IjRMN_1_yrP5m0jVI5l0t9OzfhsfLGyoJ-5E1ey9KLewlEGEGBM_B4EbinjZ5tWTrl",
      "receipt":"{\"packageName\":\"com.package.first\",\"productId\":\"iap_id\",\"purchaseTime\":1458232471621,\"purchaseState\":0,\"purchaseToken\":\"bhgenijimhhgenhadngmajnp.AO-J1OxzqrUBfYXMJinFFjbRSUhL6E7bcbfnp0uZpEWi_ziPiimWbFt4n7IjRMN_1_yrP5m0jVI5l0t9OzfhsfLGyoJ-5E1ey9KLewlEGEGBM_B4EbinjZ5tWTrl\",\"autoRenewing\":false}",
      "signature":"PmKBJWBlVcIg//lZuMaG0zIEQZMcPrJjPUipJ/m0Ccm69mAmh1nPNyy6/Du6FMDEWijEI9jpbnQjLz4/bWBuqjr2CCLImcBFnHkA+ZvslDlh5ZzjwxtC7kD6PwuOMlelqS82JhIRMv1ZwxIYdEA8+Y5XiIClmJ5qvtCcgjU8b2HXDy3lIj5GfWCXJkoE0BMVHLJZemTK4asB5VzxU2xbUrk6ugBmc5jJ0LdlDue12NhFI62edhZoMhOoWd7TJP+IadUb8fIUb4AGct3zI5ccM1pHrzwvUuU0VWxLUs5qr2zCNkz4kw=="
   },
   "valid":true
}

Upvotes: 3

Views: 2639

Answers (2)

Nathan Prather
Nathan Prather

Reputation: 2106

Okay, Here's what I have so far. this works with iOS, I'm going to have to go back and test with Android later but for now this works, here's what a product object looks like within the store.validator method:

var product = {
  "id": "propicks",
  "alias": "propicks",
  "type": "consumable",
  "state": "approved",
  "title": "BEST BETS & More!",
  "description": "BEST BETS, Pick Confidence, & ML Odds!",
  "price": "$2.99",
  "currency": "USD",
  "loaded": true,
  "canPurchase": false,
  "owned": false,
  "downloading": false,
  "downloaded": false,
  "transaction": {
    "type": "ios-appstore",
    "id": "1000000263098271",
    "appStoreReceipt": "MIIT...",
    "transactionReceipt": "ew..."
      },
  "valid": true,
  "transactions": [ "1000000263098271" ]
}

After some testing the product.transaction.transactionReceipt appears to always be the unique receipt so that's the one I'm verifying with Apple. I'm using consumables so it may be different for subscriptions.

Here's my store.validator method:

store.validator = function(product, callback) {
    app.kendoConsole.log('in STORE.VALIDATOR FUNCTION');

    var myData = {
      transactionReceipt: product.transaction.transactionReceipt,
      purchaseType: product.transaction.type
    };

    $.ajax({
      url: app.config.checkIosReceiptUrl,
      data: myData,
      type: "POST",
      beforeSend: function() {

        app.utils.showLoading(kendo.format("Verifying Receipt..."));
      },
      complete: function() {

        app.utils.hideLoading();
      },
      error: function(jqXHR, textStatus, errorThrown) {

        app.utils.hideLoading();
        app.utils.handleAjaxErrors(jqXHR);
      },
      success: function(data) {

        if (data.success) {

          callback(true, {}); // success!   

        } else {

          callback(false, "Validation Failed.");
        }
      }

    });

Alright and finally on the server, my C# code to ping Apple and make sure receipt is always unique in my system, no replay purchases allowed!

 [HttpPost]
    public JsonResult CheckIOS_Receipt( string transactionReceipt, string purchaseType )
    {
        bool isReceiptValid = false;

        // first check my database to make sure this receipt is unique.
        bool isNewPurchaseToken = InAppPurchaseBLL.IsNewPurchaseToken( transactionReceipt, purchaseType );

        if ( isNewPurchaseToken ) {

            var hasPassedReceiptValidationWithStore = InAppPurchaseBLL.IsAppleVerifiedPurchase( transactionReceipt );

            if ( hasPassedReceiptValidationWithStore ) {


                // at this point, I should save the purchase token.

                isReceiptValid = true;
            }
        }

        JsonResult data = Json( new { success = isReceiptValid } );

        return data;
    }


    /// <summary>
    /// Hit apple server and return true or false if token is legit.
    /// </summary>
    /// <param name="validPurchaseToken"></param>
    /// <param name="isTest"></param>
    public static bool IsAppleVerifiedPurchase( string validPurchaseToken, bool isTest = false ) {

        bool isAppleVerified = false;

        try {

            var json = new JObject( new JProperty( "receipt-data", validPurchaseToken ) ).ToString();

            ASCIIEncoding ascii = new ASCIIEncoding();
            byte[] postBytes = Encoding.UTF8.GetBytes( json );

            string appleURI;

            if ( isTest ) {

                appleURI = "https://sandbox.itunes.apple.com/verifyReceipt";

                //appleURI = ConfigurationManager.AppSettings[ "Apple_Test_Receipt_Validation_URL" ];
            }
            else {

                appleURI = "https://buy.itunes.apple.com/verifyReceipt";

                //appleURI = ConfigurationManager.AppSettings[ "Apple_Production_Receipt_Validation_URL" ];
            }



            WebRequest request = HttpWebRequest.Create( appleURI );
            request.Method = "POST";
            request.ContentType = "application/json";
            request.ContentLength = postBytes.Length;

            using ( var stream = request.GetRequestStream() ) {

                stream.Write( postBytes, 0, postBytes.Length );

                stream.Flush();
            }

            var sendresponse = request.GetResponse();

            string sendresponsetext = "";

            using ( var streamReader = new StreamReader( sendresponse.GetResponseStream() ) ) {

                sendresponsetext = streamReader.ReadToEnd().Trim();

            }

            if ( sendresponse.IsNotNullOrEmptyString() ) {

                JObject jsonResponse = JObject.Parse( sendresponsetext );

                int status = -1;

                int.TryParse( jsonResponse[ "status" ].ToString(), out status );

                /*
                * When validating receipts on your server, your server needs to be able to handle a 
                * production-signed app getting its receipts from Apple’s test environment. 
                * The recommended approach is for your production server to always validate receipts against 
                * the production App Store first. If validation fails with the error code “Sandbox receipt used in production”, 
                * validate against the test environment instead.
                * */

                switch ( status ) {
                    case 21007:
                        /*
                         * This receipt is from the test environment, but it was sent to the production environment 
                         * for verification. Send it to the test environment instead. 
                         * */
                         // This means I'm testing on my device and still need to validate against the sandbox url.
                        return IsAppleVerifiedPurchase( validPurchaseToken, isTest: true );

                    case 21008:
                        /*
                         * This receipt is from the production environment, but it was sent to the test environment 
                         * for verification. Send it to the production environment instead.
                         * */
                        // so I don't see this ever happening but if it does, resend it to the production envirnoment instead of the sandbox.
                        return IsAppleVerifiedPurchase( validPurchaseToken, isTest: false );


                    case 0:

                        // sweet success!  the receipt is valid. Status should be either 0 or one of the error codes described here:
                        // https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateRemotely.html#//apple_ref/doc/uid/TP40010573-CH104-SW5

                        isAppleVerified = true;

                        break;

                    default:

                        isAppleVerified = false;

                        break;
                }

            }
        }
        catch ( Exception ex ) {

            ex.Message.ToString();
        }

        return isAppleVerified;
    }

Upvotes: 1

jeko
jeko

Reputation: 307

On Android, the receipt containing the details of the transaction is available right away after a purchase, so it's included in the "transaction" field.

On iOS, the SDK requires to load it explicitly. If I'm right, this is only done by the plugin when receipt validation is configured. I suppose this is what you try to achieve. You should try to access those fields from within your own "store.validator" method.

See https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md#validator

Upvotes: 1

Related Questions