Check for already purchased IAPs with ng-Storekit on iOS

Hi,

I’m new to Ionic (and Angular JS), and am trying to make an app that has In-App purchases. I found the Angular plugin PhoneGap-InAppPurchase-iOS, then the ng-storekit version of it. I can get it to recognise an IAP that I have setup in iTunes Connect.

My question is, how can I find out if a user has already purchased that product? The IAP is a video file and I want to show a “View” option if the user has purchased it and a “Buy” option if not. With native development I would find the purchase in NSUserDefaults. Is there an easy way to access NSUserDefaults in Ionic/Angular? I had a look at a plugin called me.apla.cordova.app-preferences which seems to do what I want, but I can’t seem to get it to work.

Thanks!

Hmm, never look at ng-storekit, so I’m not sure how much help I can be. Have you asked around Stackoverflow? You may have better luck there

how about storing this information in a sqllite database?

what im doing is (premium update for my app) do the IAP and save a value to my sqlite database.
when i start the app i will read this value and the user will be able to use premium feature.

what kind of error or problem you have with me.apla.cordova.app-preferences?

Yeah, that’s the way I’ve gone with it. It makes more sense to do it like this anyway. Out of interest, what are you using to hook up to storekit?

im using this plugin https://github.com/j3k0/PhoneGap-InAppPurchase-iOS
the same as ng-stroekit using.

for communication i created a class with the needed methods.
But im planing to use ng-storekit it looks like its easier to use.

class IAP {
    list: string[] = ['my.super.duper.pro'];
    products = {};
    price = '';
    loaded: boolean;

    public init() {
        // Check availability of the storekit plugin
        if (!window.storekit) {
            console.log('In-App Purchases not available');
            return;
        }

        // Initialize
        storekit.init({
            debug: true,
            noAutoFinish: false,
            ready: this.onReady,
            purchase: this.onPurchase,
            finish: this.onFinish,
            restore: this.onRestore,
            error: this.onError,
            restoreCompleted: this.onRestoreCompleted
        });
    }

    public buy(productId) {
        storekit.purchase(productId);
    }

    public restore() {
        storekit.restore();
    }

    public fullVersion() {
        //TODO!
    }

    private onReady() {
        storekit.load(this.list, (products, invalidIds) => {
            console.log('IAPs loading done:');
            price = products[0].price;
            for (var j = 0; j < products.length; ++j) {
                var p = products[j];
                console.log('Loaded IAP(' + j + '). title:' + p.title + ' description:' + p.description + ' price:' + p.price + ' id:' + p.id);
                this.products[p.id] = p;
            }

            this.loaded = true;

            for (var i = 0; i < invalidIds.length; ++i) {
                console.log('Error: could not load ' + invalidIds[i]);
            }

        });
    }

    private onPurchase(transactionId, productId) {
        //Save to DB

        storekit.finish(transactionId);

        storekit.loadReceipts((receipts) => {
            console.log('Receipt for appStore = ' + receipts.appStoreReceipt);
            console.log('Receipt for ' + productId + ' = ' + receipts.forProduct(productId));
        });
    }

    private onFinish(transactionId, productId) {
        console.log('Finished transaction for ' + productId + ' : ' + transactionId);
    }

    private onRestore(transactionId, productId) {
        console.log("Restored: " + productId);
        //TODO!!
    }

    private onError(errorCode, errorMessage) {
        alert('Error: ' + errorMessage);
    }

    private onRestoreCompleted() {
        console.log("Restore Completed");
    }
}

(Typescript!)

ng-Storekit seems pretty straightforward to use in general, but I’m trying to use it with noAutoFinish:true (I need to confirm a file downloads before finishing the transaction) and haven’t got it working yet.

ng-storekit does not support Auto Finish = false

you could implement the autofinish stuff in $storekit.watchPurchases().then(…)
when you get type purchase or restore load your content
then call $storekit.finish.

or wait till he has implemented it :frowning:

Little update:
https://github.com/domiSchenk/ng-storekit
it would be great if you could try this!

I tried that version of ng-Storekit, but I got the following errors on app load:

2014-08-11 15:37:27.745 myApp-WC[7999:60b] Multi-tasking -> Device: YES, App: YES
2014-08-11 15:37:27.767 myApp-WC[7999:60b] Unlimited access to network resources
2014-08-11 15:37:27.860 myApp-WC[7999:60b] [CDVTimer][keyboard] 0.669003ms
2014-08-11 15:37:27.871 myApp-WC[7999:60b] [CDVTimer][file] 8.908033ms
2014-08-11 15:37:27.872 myApp-WC[7999:60b] CDVPlugin class CDVStatusBar (pluginName: statusbar) does not exist.
2014-08-11 15:37:27.873 myApp-WC[7999:60b] [CDVTimer][statusbar] 1.168966ms
2014-08-11 15:37:27.874 myApp-WC[7999:60b] [CDVTimer][TotalPluginStartup] 14.514983ms
2014-08-11 15:37:28.853 myApp-WC[7999:60b] Resetting plugins due to page load.
2014-08-11 15:37:29.317 myApp-WC[7999:60b] Finished load of: file:///var/mobile/Applications/FEF8775C-CABF-4E1F-9ADF-8DE23F804F5A/myApp-WC.app/www/index.html#/app/home
2014-08-11 15:37:29.820 myApp-WC[7999:60b] THREAD WARNING: ['InAppPurchase'] took '12.855225' ms. Plugin should use a background thread.
2014-08-11 15:37:29.822 myApp-WC[7999:60b] plugins: {"appPreferences":{}}
2014-08-11 15:37:29.824 myApp-WC[7999:60b] InAppPurchase[js]: setup ok
2014-08-11 15:37:29.826 myApp-WC[7999:60b] InAppPurchase[js]: load ["com.me.myAppIAP"]
2014-08-11 15:37:29.828 myApp-WC[7999:60b] InAppPurchase[objc]: Getting products data
2014-08-11 15:37:29.829 myApp-WC[7999:60b] InAppPurchase[objc]: Set has 1 elements
2014-08-11 15:37:29.829 myApp-WC[7999:60b] InAppPurchase[objc]:  - com.me.myAppIAP
2014-08-11 15:37:29.831 myApp-WC[7999:60b] InAppPurchase[objc]: Starting product request...
2014-08-11 15:37:29.832 myApp-WC[7999:60b] InAppPurchase[objc]: Product request started
2014-08-11 15:37:29.920 myApp-WC[7999:60b] InAppPurchase[objc]: Payment transaction updated (com.me.myAppIAP):
2014-08-11 15:37:29.922 myApp-WC[7999:60b] InAppPurchase[objc]: State: PaymentTransactionStatePurchased
2014-08-11 15:37:29.934 myApp-WC[7999:60b] true
2014-08-11 15:37:29.935 myApp-WC[7999:60b] InAppPurchase[js]: exception in options.purchase: "TypeError: 'undefined' is not a function (evaluating '_onPurchaseCallback(productId)')"
2014-08-11 15:37:34.974 myApp-WC[7999:60b] InAppPurchase[objc]: productsRequest: didReceiveResponse:
2014-08-11 15:37:34.975 myApp-WC[7999:60b] InAppPurchase[objc]: Has 1 validProducts
2014-08-11 15:37:34.977 myApp-WC[7999:60b] InAppPurchase[objc]:  - com.me.myAppIAP: myApp IAP
2014-08-11 15:37:34.980 myApp-WC[7999:60b] InAppPurchase[objc]: productsRequest: didReceiveResponse: sendPluginResult: (
        (
                {
            description = "description";
            id = "com.me.myAppIAP";
            price = "\U00a31.99";
            title = "myApp IAP";
        }
    ),
        (
    )
)
2014-08-11 15:37:34.985 myApp-WC[7999:60b] InAppPurchase[js]: load ok: { valid:[{"title":"myApp IAP","id":"com.me.myAppIAP","price":"£1.99","description":"description"}] invalid:[] }
2014-08-11 15:37:34.993 myApp-WC[7999:60b] Products Loaded

Then when I try to purchase iOS gives me the message “You’ve already purchased this but it hasn’t been downloaded.” and I get the following in the console:

2014-08-11 15:44:47.764 myApp-WC[7999:60b] InAppPurchase[objc]: About to do IAP
2014-08-11 15:44:47.765 myApp-WC[7999:60b] InAppPurchase[objc]: Payment transaction updated ((null)):
2014-08-11 15:44:47.767 myApp-WC[7999:60b] InAppPurchase[objc]: Purchasing...
2014-08-11 15:44:51.157 myApp-WC[7999:60b] InAppPurchase[objc]: Payment transaction updated ((null)):
2014-08-11 15:44:51.159 myApp-WC[7999:60b] InAppPurchase[objc]: Error ERR_PAYMENT_CANCELLED Cannot connect to iTunes Store
2014-08-11 15:44:51.161 myApp-WC[7999:60b] InAppPurchase[objc]: Error 4983503 Cannot connect to iTunes Store
2014-08-11 15:44:51.164 myApp-WC[7999:60b] InAppPurchase[objc]: State: PaymentTransactionStateFailed

hmm
can you try once with a new test account?
did you try this from simulator or device?

edit
maybe the problem is that your not logged out of you normal iTunes Account.

I was using my test user account, but I think there were some issues with iTunes Connect yesterday. Anyway, I’m using a different test user this morning, and it won’t finish the transaction.

2014-08-12 11:07:04.819 myApp-WC[10381:60b] InAppPurchase[objc]: About to do IAP
2014-08-12 11:07:04.821 myApp-WC[10381:60b] InAppPurchase[objc]: Payment transaction updated ((null)):
2014-08-12 11:07:04.822 myApp-WC[10381:60b] InAppPurchase[objc]: Purchasing...
2014-08-12 11:07:10.608 myApp-WC[10381:60b] InAppPurchase[objc]: Payment transaction updated (com.me.myAppIAP):
2014-08-12 11:07:10.614 myApp-WC[10381:60b] InAppPurchase[objc]: State: PaymentTransactionStatePurchased
2014-08-12 11:07:10.627 myApp-WC[10381:60b] true
2014-08-12 11:07:10.628 myApp-WC[10381:60b] loading file...
2014-08-12 11:07:10.629 myApp-WC[10381:60b] Server Path: http://myserver:8080/IAP/
2014-08-12 11:07:10.631 myApp-WC[10381:60b] File Path: file:///var/mobile/Applications/F64374DA-4566-4538-85EB-CF5B519FC0CF/Library/NoCloud/file.mp4
2014-08-12 11:07:10.638 myApp-WC[10381:60b] InAppPurchase[objc]: Cannot finish transaction com.me.myAppIAP.
2014-08-12 11:07:12.630 myApp-WC[10381:60b] File Loaded
2014-08-12 11:07:13.668 myApp-WC[10381:7a3b] File Transfer Finished with response code 200
2014-08-12 11:07:13.932 myApp-WC[10381:60b] Download Complete: file:///var/mobile/Applications/F64374DA-4566-4538-85EB-CF5B519FC0CF/Library/NoCloud/file.mp4
2014-08-12 11:07:13.933 myApp-WC[10381:60b] InAppPurchase[objc]: Cannot finish transaction com.me.myAppIAP.

Here’s the code I’m using:

$storekit.PurchaseCallBack(function(){

    var defer = $q.defer();
    console.log("loading file...");


    var fullServerPath = serverPath+"/file.mp4";
    var filePath = cordova.file.dataDirectory+"/file.mp4";

    $cordovaFile.downloadFile(fullServerPath, filePath, true).then(function(result){
        console.log("Download Complete: "+result.toURL());
        $storekit.finish('com.me.myAppIAP');

        $scope.activityPurchased = "View";
        $scope.apply();

        $cordovaFile.checkFile(result.toURL()).then(function(result){
            console.log("File Success!");
        }, function(err){
            console.log(err);
        });
    }, function(err){
        console.log(err);
    });

    window.setTimeout(function(){
        defer.resolve(console.log("File Loaded"));
    }, 2000);

    return defer.promise;
}).purchase('com.me.myAppIAP');

So after it doesn’t finish the transaction it get’s itself in a bit of a mess. I have to then go back and change setAutoFinish to true to complete the transaction. Any idea where I might be going wrong with this?

try to use the defer.resolve inside checkfile.

$cordovaFile.checkFile(result.toURL()).then(function(result){
     defer.resolve("File Success!");
   }, function(err){
     defer.reject("File Error!");
});

call it when everything is done, not with Timeout coze a file download can go longer than 2 secs :smiley:
I also removed a bug in my implementation.

Unfortunately there is no cancel method in this plugin as far.
And on my Device it works fine (without loading stuff).

Is there anything different with the watchPurchases() function? I’m testing with the default behaviour with both the original ng-Storekit and your version - on the watchPurchases() function in the original I get the purchase object as follows:

Purchase: {"transactionId":"1000000119931449","productId":"com.me.myAppIAP2","type":"purchase"}

And in your version I get:

Purchase: {"transactionI":"com.me.myAppIAP","type":"purchase"}

hmm it should be the same maybe the timeout has an effect on it.

my test have shown me:

  • if not bought, it works
  • if already bought you have to use restore (testing)
  • at the moment it does not work with noAutoFinish (with the plugin (not ng-storekit) it should work)

i will try to find a solution.

what you could do is wait for purchase done and then load content in this method:

  .watchPurchases()
  .then(function(purchase) {
	if (purchase.productId === 'ch.domischenk.mypresents.pro') {
		if (purchase.type === 'purchase') {
			// Your product was purchased
		} else if (purchase.type === 'restore') {
			// Your product was restored
		}
		console.log("transactionId:" + purchase.transactionId);
		console.log("productId:" + purchase.productId);
		console.log("type:" + purchase.type);
		console.log("transactionReceipt:" + purchase.transactionReceipt);
	}
})

.then works only in my implementation!