PeterStaev / nativescript-purchase

:moneybag: A NativeScript plugin for making in-app purchases!
Apache License 2.0
82 stars 28 forks source link

JS implementation code sample. #3

Closed b3nnee closed 7 years ago

b3nnee commented 7 years ago

Hi,

Would really be nice to see a working javascript sample of how to use your plugin. I have tried porting your typescript code to js on {NS} 2.3.0 but I am not getting it right, always getting the error:

java.lang.RuntimeException: Unable to start activity ComponentInfo{org.benditas.hoodat/com.tns.NativeScriptActivity}: com.tns.NativeScriptException: 
Calling js method onCreate failed

TypeError: Cannot read property 'getPurchases' of undefined
File: "/data/data/org.benditas.hoodat/files/app/tns_modules/nativescript-purchase/purchase.js, line: 89, column: 26

StackTrace: 
    Frame: function:'restorePurchases', file:'/data/data/org.benditas.hoodat/files/app/tns_modules/nativescript-purchase/purchase.js', line: 89, column: 27
    Frame: function:'homePage.init', file:'/data/data/org.benditas.hoodat/files/app/benmodel/home.js', line: 176, column: 14 (At this point I have -  purchase.restorePurchases();)
    Frame: function:'exports.init', file:'/data/data/org.benditas.hoodat/files/app/main-page.js', line: 153, column: 11
    Frame: function:'', file:'/data/data/org.benditas.hoodat/files/app/app.js', line: 10, column: 14
    Frame: function:'Observable.notify', file:'/data/data/org.benditas.hoodat/files/app/tns_modules/data/observable/observable.js', line: 158, column: 23

My references.d.ts looks like this:

/// <reference path="./node_modules/tns-core-modules/tns-core-modules.d.ts" /> Needed for autocompletion and compilation.
/// <reference path="./node_modules/nativescript-purchase/nativescript-purchase.d.ts" />

In home js:


var purchase = require("nativescript-purchase");
var product = require("nativescript-purchase/product");
var transaction = require("nativescript-purchase/transaction");

homePage.init = function() {
purchase.init(["100000_coins", "10000_coins", "1000_coins", "2000_coins", "300_coins", "5000_coins", "500_coins", "remove_ads"]);

    purchase.getProducts()
        .then(function(res) {
            return console.log(res);
        }).catch(function(e) {
            return alert(e);
        });

    purchase.restorePurchases().then(function(res) {//  <---------------------- error here (further in purchases.android.js line 89 of the plugin)
            alert(res.toString());
        }).catch(function(e) {
            return alert(e);
        }); 

    if (purchase.canMakePayments()) {
        // NOTE: 'product' must be the same instance as the one returned from getProducts()
        //purchase.buyProduct(product);
        homePage.alerter("YAY payments here", "Your account is  eligible to make payments!");
    } else {
        homePage.alerter("No payments here", "Sorry, your account is not eligible to make payments!");
    }

};

In Home.js I know var purchase is called right because I did a console.dump and got this:


 === dump(): dumping members ===
JS: {
JS:     "transactionUpdatedEvent": "transactionUpdated",
JS:     "_notify": "_notify()function _notify(eventName, data) {\n    if (!observers[eventName]) {\n        return;\n    }\n    observers[eventName].forEach(function (callback) { callback(data); });\n}",
JS:     "on": "on()function on(eventName, handler) {\n    if (!observers[eventName]) {\n        observers[eventName] = [];\n    }\n    observers[eventName].push(handler);\n}",
JS:     "off": "off()function off(eventName, handler) {\n    if (!observers[eventName]) {\n        return;\n    }\n    if (!handler) {\n        observers[eventName].splice(0);\n        return;\n    }\n    var index = observers[eventName].indexOf(handler);\n    if (index !== -1) {\n        observers[eventName].splice(index, 1);\n    }\n}",
JS:     "init": "init()function init(productIdentifiers) {\n    var nativeArray = Array.create(java.lang.String, productIdentifiers.length);\n    for (var loop = 0; loop < productIdentifiers.length; loop++) {\n        nativeArray[loop] = productIdentifiers[loop].toLowerCase();\n    }\n    ensureApplicationContext().then(function () {\n        helper = new com.tangrainc.inappbilling.InAppBillingHelper(application.android.context, nativeArray);\n    });\n    application.android.on(application.AndroidApplication.activityResultEvent, function (args) {\n        if (args.requestCode === com.tangrainc.inappbilling.InAppBillingHelper.BUY_INTENT_REQUEST_CODE) {\n            var intent = args.intent;\n            var responseCode = intent.getIntExtra(\"RESPONSE_CODE\", 0);\n            var purchaseData = intent.getStringExtra(\"INAPP_PURCHASE_DATA\");\n            var dataSignature = intent.getStringExtra(\"INAPP_DATA_SIGNATURE\");\n            var tran = void 0;\n            if (args.resultCode === android.app.Activity.RESULT_OK && responseCode === 0 && !types.isNullOrUndefined(purchaseData)) {\n                var nativeValue = new org.json.JSONObject(purchaseData);\n                nativeValue.put(\"signature\", dataSignature);\n                tran = new transaction_1.Transaction(nativeValue);\n            }\n            else {\n                tran = new transaction_1.Transaction(null);\n                tran.transactionState = transaction_1.TransactionState.Failed;\n                tran.productIdentifier = currentBuyProductIdentifier;\n                tran.developerPayload = currentBuyPayload;\n            }\n            common._notify(common.transactionUpdatedEvent, tran);\n        }\n    });\n}",
JS:     "getProducts": "getProducts()function getProducts() {\n    return new Promise(function (resolve, reject) {\n        futureToPromise(helper.getProducts())\n            .then(function (result) {\n            var productArray = [];\n            for (var loop = 0; loop < result.length; loop++) {\n                productArray.push(new product_1.Product(result[loop]));\n            }\n            resolve(productArray);\n        })\n            .catch(reject);\n    });\n}",
JS:     "buyProduct": "buyProduct()function buyProduct(product, developerPayload) {\n    var tran = new transaction_1.Transaction(null);\n    tran.transactionState = transaction_1.TransactionState.Purchasing;\n    tran.productIdentifier = product.productIdentifier;\n    tran.developerPayload = developerPayload;\n    common._notify(common.transactionUpdatedEvent, tran);\n    currentBuyProductIdentifier = product.productIdentifier;\n    currentBuyPayload = developerPayload;\n    helper.startBuyIntent(application.android.foregroundActivity, product.productIdentifier, developerPayload || \"\");\n}",
JS:     "consumePurchase": "consumePurchase()function consumePurchase(token) {\n    return new Promise(function (resolve, reject) {\n        futureToPromise(helper.consumePurchase(token)).then(resolve, reject);\n    });\n}",
JS:     "restorePurchases": "restorePurchases()function restorePurchases() {\n    futureToPromise(helper.getPurchases())\n        .then(function (result) {\n        for (var loop = 0; loop < re
JS: sult.length; loop++) {\n            var tran = new transaction_1.Transaction(null);\n            tran.originalTransaction = new transaction_1.Transaction(result[loop]);\n            tran.transactionState = transaction_1.TransactionState.Restored;\n            common._notify(common.transactionUpdatedEvent, tran);\n        }\n    });\n}",
JS:     "canMakePayments": "canMakePayments()function canMakePayments() {\n    return true;\n}"
JS: }
JS: === dump(): dumping function and properties names ===
JS: _notify()
JS: on()
JS: off()
JS: init()
JS: getProducts()
JS: buyProduct()
JS: consumePurchase()
JS: restorePurchases()
JS: canMakePayments()
JS: === dump(): finished ===
PeterStaev commented 7 years ago

Hey @b3nnee , your code seems correct, but you need to call the init method in your app.js before starting your application and not on the page where you have to use the purchases. Can you try to make this change and let me know if the problem resolves.

b3nnee commented 7 years ago

Hey Thanks Peter, I made the change and for the first time, the app did not crash. Will edit my code to ensure product details are delivered correctly and get back to you. Cheers!

UPDATE: Still getting a crash when I call purchase.restorePurchases().

It still gives me the same error as above. Maybe it could be my NativeScript version 2.3.0 that's the cause OR I am possibly doing something wrong...

erjdriver commented 7 years ago

restorePurchases() doesn't return anything - ergo the error.

restorePurchases() calls a callback on the on() method you registered.

b3nnee commented 7 years ago

Interesting, I actually figured that out but the error still remains. If getPurchases is called even without expecting anything in return I still get this:

java.lang.RuntimeException: Unable to start activity ComponentInfo{org.benditas.whodat/com.tns.NativeScriptActivity}: com.tns.NativeScriptException: 
Calling js method onCreate failed

TypeError: Cannot read property 'getPurchases' of undefined
File: "/data/data/org.benditas.whodat/files/app/tns_modules/nativescript-purchase/purchase.js, line: 89, column: 26

StackTrace: 
    Frame: function:'restorePurchases', file:'/data/data/org.benditas.whodat/files/app/tns_modules/nativescript-purchase/purchase.js', line: 89, column: 27
    Frame: function:'homePage.init', file:'/data/data/org.benditas.whodat/files/app/benmodel/home.js', line: 223, column: 19
    Frame: function:'exports.init', file:'/data/data/org.benditas.whodat/files/app/main-page.js', line: 160, column: 11
    Frame: function:'', file:'/data/data/org.benditas.whodat/files/app/app.js', line: 16, column: 14
    Frame: function:'Observable.notify', file:'/data/data/org.benditas.whodat/files/app/tns_modules/data/observable/observable.js', line: 158, column: 23

In Home.js line line 218 - 236:

 product = mypurchase.getProducts();

      homePage.alerter("Products", product);

       mypurchase.restorePurchases();

       console.log("Purchase restored....");

        if (mypurchase.canMakePayments()) {

            // NOTE: 'product' must be the same instance as the one returned from getProducts()
            //purchase.buyProduct(product);
            homePage.alerter("YAY payments here", "Your account is  eligible to make payments!");

        } else {
            homePage.alerter("No payments here", "Sorry, your account is not eligible to make payments!");
        } 

In the plugin's purchases.android.js code,

This is where the issue is:

function restorePurchases() {
    futureToPromise(helper.getPurchases())   // <------ Issue is here, for some reason, helper is undefined!
        .then(function (result) {
        for (var loop = 0; loop < result.length; loop++) {
            var tran = new transaction_1.Transaction(null);
            tran.originalTransaction = new transaction_1.Transaction(result[loop]);
            tran.transactionState = transaction_1.TransactionState.Restored;
            common._notify(common.transactionUpdatedEvent, tran);
        }
    });
}

This is a console dump of the mypurchase variable in home.js:


 === dump(): dumping members ===
JS: {
JS:     "transactionUpdatedEvent": "transactionUpdated",
JS:     "_notify": "_notify()function _notify(eventName, data) {\n    if (!observers[eventName]) {\n        return;\n    }\n    observers[eventName].forEach(function (callback) { callback(data); });\n}",
JS:     "on": "on()function on(eventName, handler) {\n    if (!observers[eventName]) {\n        observers[eventName] = [];\n    }\n    observers[eventName].push(handler);\n}",
JS:     "off": "off()function off(eventName, handler) {\n    if (!observers[eventName]) {\n        return;\n    }\n    if (!handler) {\n        observers[eventName].splice(0);\n        return;\n    }\n    var index = observers[eventName].indexOf(handler);\n    if (index !== -1) {\n        observers[eventName].splice(index, 1);\n    }\n}",
JS:     "init": "init()function init(productIdentifiers) {\n    var nativeArray = Array.create(java.lang.String, productIdentifiers.length);\n    for (var loop = 0; loop < productIdentifiers.length; loop++) {\n        nativeArray[loop] = productIdentifiers[loop].toLowerCase();\n    }\n    ensureApplicationContext().then(function () {\n        helper = new com.tangrainc.inappbilling.InAppBillingHelper(application.android.context, nativeArray);\n    });\n    application.android.on(application.AndroidApplication.activityResultEvent, function (args) {\n        if (args.requestCode === com.tangrainc.inappbilling.InAppBillingHelper.BUY_INTENT_REQUEST_CODE) {\n            var intent = args.intent;\n            var responseCode = intent.getIntExtra(\"RESPONSE_CODE\", 0);\n            var purchaseData = intent.getStringExtra(\"INAPP_PURCHASE_DATA\");\n            var dataSignature = intent.getStringExtra(\"INAPP_DATA_SIGNATURE\");\n            var tran = void 0;\n            if (args.resultCode === android.app.Activity.RESULT_OK && responseCode === 0 && !types.isNullOrUndefined(purchaseData)) {\n                var nativeValue = new org.json.JSONObject(purchaseData);\n                nativeValue.put(\"signature\", dataSignature);\n                tran = new transaction_1.Transaction(nativeValue);\n            }\n            else {\n                tran = new transaction_1.Transaction(null);\n                tran.transactionState = transaction_1.TransactionState.Failed;\n                tran.productIdentifier = currentBuyProductIdentifier;\n                tran.developerPayload = currentBuyPayload;\n            }\n            common._notify(common.transactionUpdatedEvent, tran);\n        }\n    });\n}",
JS:     "getProducts": "getProducts()function getProducts() {\n    return new Promise(function (resolve, reject) {\n        futureToPromise(helper.getProducts())\n            .then(function (result) {\n            var productArray = [];\n            for (var loop = 0; loop < result.length; loop++) {\n                productArray.push(new product_1.Product(result[loop]));\n            }\n            resolve(productArray);\n        })\n            .catch(reject);\n    });\n}",
JS:     "buyProduct": "buyProduct()function buyProduct(product, developerPayload) {\n    var tran = new transaction_1.Transaction(null);\n    tran.transactionState = transaction_1.TransactionState.Purchasing;\n    tran.productIdentifier = product.productIdentifier;\n    tran.developerPayload = developerPayload;\n    common._notify(common.transactionUpdatedEvent, tran);\n    currentBuyProductIdentifier = product.productIdentifier;\n    currentBuyPayload = developerPayload;\n    helper.startBuyIntent(application.android.foregroundActivity, product.productIdentifier, developerPayload || \"\");\n}",
JS:     "consumePurchase": "consumePurchase()function consumePurchase(token) {\n    return new Promise(function (resolve, reject) {\n        futureToPromise(helper.consumePurchase(token)).then(resolve, reject);\n    });\n}",
JS:     "restorePurchases": "restorePurchases()function restorePurchases() {\n    futureToPromise(helper.getPurchases())\n        .then(function (result) {\n        for (var loop = 0; loop < re
JS: sult.length; loop++) {\n            var tran = new transaction_1.Transaction(null);\n            tran.originalTransaction = new transaction_1.Transaction(result[loop]);\n            tran.transactionState = transaction_1.TransactionState.Restored;\n            common._notify(common.transactionUpdatedEvent, tran);\n        }\n    });\n}",
JS:     "canMakePayments": "canMakePayments()function canMakePayments() {\n    return true;\n}"
JS: }
JS: === dump(): dumping function and properties names ===
JS: _notify()
JS: on()
JS: off()
JS: init()
JS: getProducts()
JS: buyProduct()
JS: consumePurchase()
JS: restorePurchases()
JS: canMakePayments()
JS: === dump(): finished ===

The greatest help you could give now is simply ignore all what I have written here and create a sample of how to use your plugin via JS not typescript. If it works, I could use that as a guide to getting my code to work.

Thanks for your time, cheers!

PeterStaev commented 7 years ago

Hey @b3nnee , the helper is initialized when you call init so if you do that in the app.js not sure how it can end up undefined. Since TS transpiles to JS, you can just clone this repo and run tns build android in the sample dir. This will generate JS files from the TS ones and you can look in those.

b3nnee commented 7 years ago

OK, great will do that, thanks.