Check for already purchased IAPs with ng-Storekit on iOS


#1

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!


#2

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


#3

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?


#4

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?


#5

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!)


#6

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.


#7

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!


#8

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

#9

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.


#10

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?


#11

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).


#12

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"}

#13

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!