j3k0 / cordova-plugin-purchase

In-App Purchase for Cordova on iOS, Android and Windows
https://purchase.cordova.fovea.cc
1.29k stars 529 forks source link

Windows Universal errors (simulator) #294

Closed rafaellop closed 5 years ago

rafaellop commented 8 years ago

Hi,

I know the Windows version is beta, but I'd like to try it. However I'm getting errors but maybe I'm doing something wrong? I read the windows.md and I set the Test mode to utilize the local IAP simulator. There's also XML in the www but my app first reports some errors and then tries to connect to the store and then shows a message that the app should be bought first in the Windows Store which is stramge because the app is free and only IAPs are defined. Here is my code and the XML:

$scope.initInAppPayments = function() {

        store.verbosity = store.DEBUG;

        store.inappbilling.setTestMode(true);

        store.register( {
           id:   'iap_1',
           type: store.NON_CONSUMABLE
        });

        store.ready(function() {
            console.log("\\o/ STORE READY \\o/");
        });

        store.error(function(error) {
            console.log('STORE ERROR 1: ' + error.code + ': ' + error.message);
            store.refresh();
        });

        store.when("product").updated(function (p) {
            $scope.updateLocalInAppProductsInfo(p);
        });

        store.when("product").error(function (error) {
            console.log('STORE ERROR 2: ' + error.code + ': ' + error.message);
        });

        store.when("product").approved(function(p) {
            p.verify();
//            p.finish();
        });

        store.when("product").verified(function(p) {
            p.finish();
        });

        store.when("product").cancelled(function (p) {
            console.log('PRODUCT CANCELLED');
        });

        store.refresh();
}

Here is the XML:

<?xml version="1.0" encoding="utf-16" ?>
<CurrentApp>
  <ListingInformation>
    <App>
      <AppId>988b90e4-5d4d-4dea-99d0-e423e414ffbc</AppId>
      <LinkUri>http://apps.microsoft.com/webpdp/app/988b90e4-5d4d-4dea-99d0-e423e414ffbc</LinkUri>
      <CurrentMarket>en-us</CurrentMarket>
      <AgeRating>3</AgeRating>
      <MarketData xml:lang="en-us">
        <Name>CordovaApp</Name>
        <Description>CordovaApp</Description>
        <Price>0.00</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>        
      </MarketData>
    </App>
    <Product ProductId="iap_1">
      <MarketData xml:lang="en-us">
        <Name>IAP product1</Name>
        <Price>1.99</Price>
        <CurrencySymbol>$</CurrencySymbol>
        <CurrencyCode>USD</CurrencyCode>
      </MarketData>
    </Product>
  </ListingInformation>
  <LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
    </App>
    <Product ProductId="iap_1">
      <IsActive>False</IsActive>
    </Product>
  </LicenseInformation>
</CurrentApp>

Debug log with the error at the end (detailed debug log for the plugin):

[store.js] DEBUG: store.queries !! 'iap_1 registered'
[store.js] DEBUG: store.queries !! 'non consumable registered'
[store.js] DEBUG: store.queries !! 'registered'
[store.js] DEBUG: store.queries !! 'iap_1 updated'
[store.js] DEBUG: store.queries !! 'non consumable updated'
[store.js] DEBUG: store.queries !! 'updated'
[store.js] DEBUG: queries ++ 'updated'
[store.js] DEBUG: queries ++ 'error'
[store.js] DEBUG: queries ++ 'approved'
[store.js] DEBUG: queries ++ 'verified'
[store.js] DEBUG: queries ++ 'unverified'
[store.js] DEBUG: queries ++ 'cancelled'
[store.js] DEBUG: queries ++ 'refunded'
[store.js] DEBUG: store.trigger -> triggering action refreshed
[store.js] DEBUG: queries !! 'refreshed'
InAppBilling[js]: setup ok
InAppBilling[js]: load ["iap_1"]
[store.js] DEBUG: plugin -> ready
InAppBilling[js]: getAvailableProducts called!
**MOJ STORE ERROR 6777002: Loading product info failed - WinRTError: Nie można odnaleźć elementu.**

[store.js] DEBUG: store.trigger -> triggering action refreshed
[store.js] DEBUG: queries !! 'refreshed'
[store.js] DEBUG: refresh -> checking products state (1 products)
[store.js] DEBUG: refresh -> product id iap_1 (iap_1)
[store.js] DEBUG:            in state 'registered'
[store.js] DEBUG: store.trigger -> triggering action re-refreshed
[store.js] DEBUG: queries !! 're-refreshed'
InAppBilling[js]: getPurchases called!
licenses 
   "licenses"
   [
      length: 0
   ]

\o/ STORE READY \o/

The (bolded) error happens for the this.currentApp.loadListingInformationAsync() call in the src/windows/InAppPurchaseProxy.js line 61

If I now call store.order() here is what happens:

InAppBilling[js]: buy called!
[store.js] DEBUG: windows -> product data for iap_1 
[store.js] DEBUG: {}
[store.js] DEBUG: undefined
[store.js] DEBUG: store.queries !! 'iap_1 cancelled'
[store.js] DEBUG: store.queries !! 'non consumable cancelled'
[store.js] DEBUG: store.queries !! 'valid cancelled'
[store.js] DEBUG: store.queries !! 'cancelled'
PRODUCT CANCELLED

For me it seems that the store doesn't have the in-app-purchase.xml for simulation loaded, but I have no idea why because for me everything seems ok.

Please help me with that if you can.

Cheers, Rafal

dkarzon commented 8 years ago

@rafaellop Can you provide a sample project? Everything there looks fine to me.

Do you get anything displayed when you make the order call?

rafaellop commented 8 years ago

Hi,

@rafaellop https://github.com/rafaellop Can you provide a sample project? Everything there looks fine to me.

I can prepare one.

Do you get anything displayed when you make the order call?

Yes, a message that the app must be first bought from the store. However, have you noticed that calling the store.order cause this:

InAppBilling[js]: buy called! [store.js] DEBUG: windows -> product data for iap_1 [store.js] DEBUG: {} [store.js] DEBUG: undefined

It seems to me that the xml isn't loaded to the sim. And above there's the error:

MOJ STORE ERROR 6777002: Loading product info failed - WinRTError: The item cannot be found.

Check please the attached screenshot of the store message. It says in English:

"Buy full version To purchase IAP inside application you must first buy the full version of the MyGI app in the Store".

Thanks :)

Cheers, Rafal

dkarzon commented 8 years ago

@rafaellop The attachment didn't make it to Github. Also what version of Windows are you testing this on? Phone 8.1? Store 8.1? Windows 10? Windows 10 Mobile?

Also a side note: Your store event queries are incorrect. They should be store.when("iap_1")...

rafaellop commented 8 years ago

I'm testing on Windows Phone 8.1 and on Windows 10 Pro.

The screenshots are here (both from Phone and desktop): W10: http://1drv.ms/1WlGiLa WP8.1: http://1drv.ms/1WlGjyS

They are Polish lang. First says: "Buy full version / To purchase IAP inside application you must first buy the full version of the MyGI app in the Store" The second: "An error occured during processing the request. Try again later. Error code for interested: 805a0194"

Internet is of course ON and the account is OK, I can make purchases.

And regarding your comment about queries, the event handler was meant to be universal and I left the "product" as in the example project according to the queries docs for better code readibility:

https://github.com/j3k0/cordova-plugin-purchase/blob/master/doc/api.md#queries "product" or "order" - for all products.

But anyhow, changing this to the product id (iap_1) doesn't make any difference.

Cheers, R.

rafaellop commented 8 years ago

What's interesting that even if I change the license info in the XML file to e.g.

<LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
    </App>
    <Product ProductId="iap_1">
      <IsActive>True</IsActive>
    </Product>
  </LicenseInformation>

The debug log shows

InAppBilling[js]: getPurchases called!
licenses 
   "licenses"
   [
      length: 0
   ]
dkarzon commented 8 years ago

Do you get the same error if you take out the Product element from LicenseInformation?

rafaellop commented 8 years ago

Yes, same thing: "Loading product failed". Doesn't matter if the IsActive is true or false it seems that the xml is not loaded into sim. It just fails (error callback to fail function) in the promise in InAppPurchaseProxy.js for some reason:

getProductDetails: function (win, fail, args) {
        // get the listing information for the products this app supports
        this.currentApp.loadListingInformationAsync().then(

Hmm... Are you sure the function call is OK? The MSDN docs syntax for this function is:

this.currentApp.loadListingInformationAsync().done() not this.currentApp.loadListingInformationAsync().then()

https://msdn.microsoft.com/en-us/library/windows/apps/windows.applicationmodel.store.currentapp.loadlistinginformationasync

I cannot verify this because each time I change this in the InAppPurchaseProxy.js, VS2015 redownloads the plugin from the repo during build and at the moment now I'm too tired (1:23 AM) to handle this...

R

rafaellop commented 8 years ago

Ok, I got the bug :-)

File: src/windows/InAppPurchaseProxy.js

It is in the repository:

module.exports = {
    setTestMode: function(args){ ... }

It should be:

module.exports = {
    setTestMode: function(win, fail, args){ ... }

The test mode was never initialized because the args parameter was undefined. This little change and everything works like a breeze :-)

@dkarzon tell me please if it is now enough to just comment out the testmode call and the final version will work with real Store (of course the store iaps configured).

@j3k0 Please update the repository if possible.

Cheers, Rafal

rafaellop commented 8 years ago

Was happy too soon. Although the above troubles are gone, there are still some other issues:

1) The store.inappbilling.setTestMode is async but doesn't return promises or fire events so that we cannot register products in the right moment and we got a lot of invalid products errors when registering own products and the store plugin knows nothing about them.

2) When the XML is finally loaded there's no event or callback that would notify the app and allow it to for example update screen info.

3) Because of the above if there is some license information about a durable (non-consumable) product in the XML and (in the real store?), the app cannot also find the license information (it doesn't know if and when it has been loaded).

I suppose both of these will also have impact in the real world (not on iap sim) thus it is for example impossible to update the UI with prices/descriptions downloaded from the Windows Store when they are loaded finally. The only thing I get is the console log: got the xml file for currentAppSimulator [object Windows.Storage.StorageFile] and loaded xml file which are available after store.ready().

I don't know how to handle this at the moment. Or maybe these are only simulator issues and the real store works correctly firing events in the same moments like e.g. android version? Please advice.

R.

dkarzon commented 8 years ago

@rafaellop I was having a look at this yesterday and found the same thing about the parameters. my solution was to take out the bool completely and have that method set it to test mode and only make that call when you want to use test mode.

I think this may have been a change made in one of the later versions of Cordova. I'll have a look at updating it.

dkarzon commented 8 years ago

@rafaellop Not too sure about the answers for your questions about async as this call is only used to test the app and when it's live it will be pulling this data from the store as part of the initialization of the store plugin process. That kind of thing should probably be done using a getAvailableProducts call.

Otherwise I have just pushed an update to my fork with callbacks added to the testmode function. https://github.com/dkarzon/cordova-plugin-purchase/blob/master/doc/windows.md

I have also added a fix for loading the xml config file which I'll PR soon. Can you give it a try and let me know if it works?

rafaellop commented 8 years ago

@dkarzon Thanks for the fix. I can now use callbacks when the XML is loaded but this didn't changed much unfortunately. It seems that even the XML contains valid data and it is loaded it doesn't update products. I've tested different scenarios:

Test 1 (old way):

Results of test 1:

InAppBilling[js]: setup ok
InAppBilling[js]: load ["iap_1"]
[store.js] DEBUG: plugin -> ready
InAppBilling[js]: getAvailableProducts called!
listing [object Windows.ApplicationModel.Store.ListingInformation] {} // DEFAULT LICENSE NOT XML
[store.js] DEBUG: plugin -> loaded - [{},{}]
[store.js] DEBUG: store.queries !! 'iap_1 invalid'
[store.js] DEBUG: store.queries !! 'updated'
product UPDATED 
INVALID PRODUCT: iap_1 alias: iap_1{"id":"iap_1","alias":"iap_1","type":"non consumable","state":"invalid","title":null,"description":null,"price":null,"currency":null,"loaded":true,"valid":false,"canPurchase":false,"owned":false,"downloading":false,"downloaded":false,"transaction":null}

Test 2:

Results: The same because TestMode is Async and it is finished after products are registered.

Test 3:

store.inappbilling.setTestMode(function(data) {
    $scope.registerIAPProducts();
});

Results:

InAppBilling[js]: setup ok
[store.js] DEBUG: plugin -> ready
InAppBilling[js]: getAvailableProducts called!
listing [object Windows.ApplicationModel.Store.ListingInformation] {}
\o/ STORE READY \o/
got the xml file for currentAppSimulator [object Windows.Storage.StorageFile]{}}
loaded xml file
check prods13
register: iap_1 / false
[store.js] DEBUG: store.queries !! 'iap_1 registered'
[store.js] DEBUG: store.queries !! 'non consumable registered'
[store.js] DEBUG: store.queries !! 'registered'
[store.js] DEBUG: store.queries !! 'iap_1 updated'
[store.js] DEBUG: store.queries !! 'non consumable updated'
[store.js] DEBUG: store.queries !! 'updated'
product UPDATED 
update local info for {"id":"iap_1","alias":"iap_1","type":"non consumable","state":"registered","title":null,"description":null,"price":null,"currency":null,"loaded":false,"canPurchase":false,"owned":false,"downloading":false,"downloaded":false,"transaction":null}

The XML is loaded just after STORE READY and contains correct data. The license loaded before STORE READY is a default license with some sample products from the IAP SDK I think. Unfortunately registering products in the settestmode callback just put them into the store but their data is not updated as we can see when listy the p object passed to the UPDATED event.

Test 4:

store.inappbilling.setTestMode(function(data) {
    $scope.registerIAPProducts();
    store.refresh();
});

Added store refreshing after products are registered.

Results:

InAppBilling[js]: setup ok
[store.js] DEBUG: plugin -> ready
InAppBilling[js]: getAvailableProducts called!
listing [object Windows.ApplicationModel.Store.ListingInformation]
\o/ STORE READY \o/
got the xml file for currentAppSimulator [object Windows.Storage.StorageFile]
loaded xml file
register: iap_1 / false
[store.js] DEBUG: store.queries !! 'iap_1 registered'
[store.js] DEBUG: store.queries !! 'non consumable registered'
[store.js] DEBUG: store.queries !! 'registered'
[store.js] DEBUG: store.queries !! 'iap_1 updated'
[store.js] DEBUG: store.queries !! 'non consumable updated'
[store.js] DEBUG: store.queries !! 'updated'
product UPDATED 
update local info for {"id":"iap_1","alias":"iap_1","type":"non consumable","state":"registered","title":null,"description":null,"price":null,"currency":null,"loaded":false,"canPurchase":false,"owned":false,"downloading":false,"downloaded":false,"transaction":null}
[store.js] DEBUG: store.trigger -> triggering action refreshed
[store.js] DEBUG: queries !! 'refreshed'
[store.js] DEBUG: refresh -> checking products state (1 product)
[store.js] DEBUG: refresh -> product id iap_1 (iap_1)
[store.js] DEBUG:            in state 'registered'
[store.js] DEBUG: store.trigger -> triggering action re-refreshed
[store.js] DEBUG: queries !! 're-refreshed'
InAppBilling[js]: getPurchases called!
licenses [object Object]
[store.js] WARNING: plugin -> user owns a non-registered product

The only change after refresh is that the store reports that the user owns a non-regiestered product which is not quite true because it's been regiestered above and the XML license info info says:

<LicenseInformation>
    <App>
      <IsActive>true</IsActive>
      <IsTrial>false</IsTrial>
    </App>
    <Product ProductId="iap_1">
      <IsActive>true</IsActive>
    </Product>
  </LicenseInformation>

I'm now out of ideas how to make this work together. Maybe if the store loaded the simulator listing information XML at the very beginning and use it instead of the default license in the line listing [object Windows.ApplicationModel.Store.ListingInformation] (which is visible in the log just after [store.js] DEBUG: plugin -> ready before I can even register my own products, it would work correctly. S, maybe the setmode should be set at the initialization state so that the loadListingInformationAsync() in the getAvailableProducts in InAppPurchaseProxy.js was called on already loaded simulator products listing?

Rafal

dkarzon commented 8 years ago

@rafaellop Hmm that may be an issue with the format of the object returned by getPurchases. Are you able to debug that call and check the purchases object returned?

rafaellop commented 8 years ago

@dkarzon The default license object returned by InAppBilling[js]: getAvailableProducts is:

listing [object Windows.ApplicationModel.Store.ListingInformation]
   "listing"
   {
      [functions]: ,
      __proto__: { },
      ageRating: 3,
      currentMarket: "US",
      description: "AppDescription",
      formattedPrice: "1,00 $",
      name: "AppName",
      productListings: {
         [functions]: ,
         1: {
            [functions]: ,
            __proto__: { },
            description: "",
            formattedPrice: "1,00 $",
            imageUri: null,
            keywords: { },
            name: "Product1Name",
            productId: "1",
            productType: 1,
            tag: ""
         },
         2: {
            [functions]: ,
            __proto__: { },
            description: "",
            formattedPrice: "1,00 $",
            imageUri: null,
            keywords: { },
            name: "Product2Name",
            productId: "2",
            productType: 2,
            tag: ""
         },
         __proto__: { },
         size: 2
      }
   }

and the object returned by the next called getPurchases is:

licenses 
   "licenses"
   [
      length: 0
   ]

then I load the simulator XML and what it reads in getPurchases is:

licenses [object Object]
   "licenses"
   [
      0: {
         [functions]: ,
         __proto__: { },
         license: {
            [functions]: ,
            __proto__: { },
            expirationDate: [date] Fri Dec 31 9999 01:00:00 GMT+0100,
            isActive: true,
            isConsumable: <No permissions>,
            productId: "iap_1"
         }
      },
      length: 1
   ]

I think the issue is not with the format and the object but rather with the fact that the simulator don't update the store object.

Cheers, Rafal

dkarzon commented 8 years ago

I'm wondering if that is just a side effect of the IAP simulator. May need to submit a beta to the store to test that for real.

rafaellop commented 8 years ago

@dkarzon I'll try but tell me please if you use the plugin on Windows platform in a public released app and it works?

rafaellop commented 8 years ago

@dkarzon The Windows platform is so funny... The simplest things are so twisted like some crazy guy on a good trip did this. I'm afraid I'm unable to verify this quickly because first I;ve realized there's no beta option.You can only publish app as hidden and make it available to a listed email addresses. They promise beta apps won't go through the whole certification, but it still took 24 hours to see the app in the store. Unfortunately even though I added correct email addresses to the allowed list I'm unable to install with the message that I'm not allowed. Jeez... So I build and installed a copy locally with the same package name but the store reports the same old good error "STORE ERROR 6777002: Loading product info failed - WinRTError: The item cannot be found." and I can do nothing. It seems that I would need to publish live app just to test iaps. My gosh... I've though Apple's way is sick.

P.S. Of course IAPs are configured in the Windows Store dashboard. Test mode is off.

webbprofessor commented 8 years ago

After reading the above issues regarding the simulator, I've decided to bypass it completely and publish my app to the Windows Store with IAP options only visible to Admins. While this plugin works perfect on both the iOS and Android versions of the app, store.order does not work on the Windows Phone.

I also noticed the Runtime Error: "6777002 - Loading product info failed - WinRTE" in the JavaScript Console during my initial tests.

rafaellop commented 8 years ago

And what is your experience with the admins? Are you able to use IAPs from your admins' accounts? I'd love if someone told me whether the plugin works on Windows Phone or completely not (not sim). That would save a lot of time :)

webbprofessor commented 8 years ago

I am unfortunately unable to get IAPs to work via the mentioned admin accounts. I forwarded this thread and runtime error to the Visual Studio Cordova Tools team at Microsoft. Hopefully they'll be able to add their two cents.

rafaellop commented 8 years ago

@dkarzon I've downloaded your game and on Windows Phone your IAP works but on WIndows 10 I've got error. Do you use the plugin?

rafaellop commented 8 years ago

@webbprofessor Please share here what they will respond to you.

webbprofessor commented 8 years ago

More detail regarding published app:

image

image

rafaellop commented 8 years ago

Same here. I've just been able to do a test of the store on my published app and IAP fails. I've got a screenshot what the store says: http://1drv.ms/1j1hrhK

I cannot debug this app but it seems to me that the issue is with the initial store association. There's no link to the Windows Phone or Windows Store and some kind of a default products is used instead of data read from the Windows Store backend. The store knows nothing about the IAPs in the dashboard and displays such errors. I'm out of ideas...

r.

rafaellop commented 8 years ago

@dkarzon @webbprofessor @j3k0 Good news guys, I've found the source of all these troubles. There's a bug in the getAvailableProducts() in InAppPurchaseProxy.js.

It is now:

    getAvailableProducts: function (win, fail, args) {
        // get the listing information for the products this app supports
        this.currentApp.loadListingInformationAsync().done(
            function (listing) {

                console.log("listing", listing);
                var productListings = listing.productListings;
                var products = [];
                // loadListingInformationAsync returns the ListingInformation object in listing.
                // get the product listing collection from the ProductListings property.
                for (var productId in productListings) if (productListings.hasOwnProperty(productId)) {
                    var product = productListings.lookup(productId);
                    if (product) {
                        products.push(product);
                    }
                }
                win(products);
            },
            fail
        );
    },

it should be:

getAvailableProducts: function (win, fail, args) {
        // get the listing information for the products this app supports
        this.currentApp.loadListingInformationAsync().done(
            function (listing) {

                console.log("listing", listing);
                var productListings = listing.productListings;
                var products = [];
                // loadListingInformationAsync returns the ListingInformation object in listing.
                // get the product listing collection from the ProductListings property.
                var iterator = listing.productListings.first();
                while (iterator.hasCurrent) {
                    products.push(iterator.current.value);
                    iterator.moveNext();
                }
                win(products);
            },
            fail
        );
    },

Why? It's because the listing object is a MapView object not an array. As MapView it doesn't support index-based lookup not the foreach method. Instead the iterator is used which can be obtained throught the first method of the MapView object.

When I changed the code it's like a magic wand touch. Everything works. No WinRT errors. :-)

However there's still one issue with the Simulator and I don't have time to fix it so I hope @dkarzon could fix this maybe as this is probably his code? Setting the store to be in test mode doesn't cause the store to update products and recall the getPurchases and getAvailableProducts. That would be the final touch and I would consider the WP/Windows version of the plugin non alfa anymore :-)

@webbprofessor Please check my fix if possible in your app.

@dkarzon @j3k0 please update the repo after confirmed fix.

j3k0 commented 8 years ago

@rafaellop Good job!

Have any idea if this would break the android version?

@dkarzon will you handle integrating that fix?

rafaellop commented 8 years ago

@j3k0 I think it shouldn't break Android version because the change is in only one file under the windows folder: cc.fovea.cordova.purchase\src\windows\InAppPurchaseProxy.js

Regarding the Android and Windows Phone, is there a way to make the Visual Studio stops complaining about missing BILLING_KEY in the config.xml?

webbprofessor commented 8 years ago

I'd just add a fake BILLING_KEY if the error is bothering you.

  <vs:plugin name="cc.fovea.cordova.purchase" version="4.0.0" src="https://github.com/j3k0/cordova-plugin-purchase.git">
<param name="BILLING_KEY" value="Your Fake Billing Key" /> </vs:plugin>

This fix did not work for me via the Emulator (I will do a test on a physical device tonight). How were you able to test it @rafaellop ?

rafaellop commented 8 years ago

@webbprofessor yes, I use the same trick, but it would be great to have it out of the box.

I'm waiting for my app to be published in the store to test the above fix. The simulator doesn't work correctly because of the flow issues described above.

rafaellop commented 8 years ago

Ok, the app has just been published in the store so I did a quick test order on my Windows Phone device. Everything went smoothly and my credit card was charged.

However on my desktop Windows 10 I'm getting the same error like with @dkarzon app mentioned above so I think it must be something rather related to my MS account configuration.

Tommorow I'll do some more tests from the VS debugger to see the communication.

Good news is that it is finally possible to use the plugin on Windows platform and it works almost the same as for Android and iOS (must check how events are fired). I've finally got to the point when my IAP handling code is exactly the same as on Android without ay platform specific conditionals :-)

webbprofessor commented 8 years ago

That's is great news!! Do you mind sharing everything IAP related in your config.xml so that I can be confident the InAppPurchaseProxy.js customization (bug fix) gets picked up when I submit to the store?

Thanks!

dkarzon commented 8 years ago

@rafaellop Thanks for your help on this! image

Planning on taking a look over the weekend. I'll checkout your fix for getAvailableProducts, do you have a fork you were working in or just inline in your project? This shouldn't affect other platforms as it's in the windows only part of the plugin.

As for your issue with the BILLING_KEY parameter, I don't really like this either but it's out of my hands (not my project and Android devs need it). I have started some notes about it in my repo: https://github.com/dkarzon/cordova-plugin-purchase/blob/master/doc/windows.md

I'll also put some code up for a sample project I have that uses this plugin.

rafaellop commented 8 years ago

Unfortunately there are still serious errors! Investigating...

rafaellop commented 8 years ago

Ok, I've got two things that are important and stopped me from releasing my app to the public. I've been worried that my purchase was not restored from the Windows Store after uninstall/install of my app and found these two errors:

Here's the first one: cc.fovea.cordova.purchase\src\windows\InAppPurchaseProxy.js

it is:

    getPurchases: function (win, fail, args) {
        var licenses = [];
        // now get a specific licenses.
        for (var productId in this.productLicenses){
            if (this.productLicenses.hasOwnProperty(productId)) {
                licenses.push({ license: this.productLicenses.lookup(productId) });
            }
        }
        console.log("licenses", licenses);
        win(licenses);
    },

it should be:

    getPurchases: function (win, fail, args) {
        var licenses = [];
        // now get a specific licenses.
        for (var productId in this.currentApp.licenseInformation.productLicenses) {
            if (this.currentApp.licenseInformation.productLicenses.hasOwnProperty(productId)) {
                licenses.push({ license: this.currentApp.licenseInformation.productLicenses.lookup(productId) });
            }
        }
        console.log("licenses", licenses);
        win(licenses);
    },

What was wrong: Even if a user owns some IAP items the store plugin knows nothing about them because licenses object is empty.

Why so? The function knows nothing about this.productLicenses because it is declared only once and only in the Simulator mode (test). Replaced with this.currentApp.licenseInformation.productLicenses causes the licenses are correctly fetched.

Here's the second one: cc.fovea.cordova.purchase\www\store-windows.js

it is:

function iabGetPurchases() {
        store.inappbilling.getPurchases(function(purchases) {
            if (purchases && purchases.length) {
                for (var i = 0; i < purchases.length; ++i) {
                    var purchase = purchases[i];
                    var p = store.get(purchase.productId);
                    if (!p) {
                        store.log.warn("plugin -> user owns a non-registered product");
                        continue;
                    }
                    store.setProductData(p, purchase);
                }
            }
            store.ready(true);
        }, function() {});
    }

it should be:

    function iabGetPurchases() {
        store.inappbilling.getPurchases(function(purchases) {
            if (purchases && purchases.length) {
                for (var i = 0; i < purchases.length; ++i) {
                    var purchase = purchases[i];
                    var p = store.get(purchase.license.productId);
                    if (!p) {
                        store.log.warn("plugin -> user owns a non-registered product");
                        continue;
                    }
                    store.setProductData(p, purchase);
                }
            }
            store.ready(true);
        }, function() {});
    }

What was wrong: Even though the store knows about owned items, the above function reports the error "user owns a non-registered product" because it cannot associate the productId from license with an item registered in the store plugin.

Why so? The purchase is not a product. It is a purchase object so we can access the productId property only through the license property.

After these changes purchases are restored and the store.updated event is fired and allows to update the UI.

The above changes shouldn't affect other platforms. They are only in the Windows specific files.

Warnings:

1) My app is free + iaps so I don't know and cannot test how this modification works in the case of paid/trial app. The isActive and isTrial app properties are in the licenseInformation object while IAP licenses are inside licenseInformation.productLicenses and only this is checked in the getPurchases function and in the other places.

2) My app contains only durables (non-consumable items) so I didn't check how the consumePurchase function works.

3) I can see that the subcribe is not implemented, but already at the moment it contains the improper use of this.productLicenses as it is used in the getPurchases.

4) The getProductDetails uses the same for loop as it's been used in the getAvailableProducts (replaced with the iterator) so I expect the getProductDetails also doesn't work.

rafaellop commented 8 years ago

@dkarzon 1) please check the two above new bug fixes 2) I didn't fork the project. I work locally but I tried to explain the changes in details. Sorry, but it's my workflow. I'm used to my local svn :)

@webbprofessor My config.xml doesn't contain anything special. Just the plugin reference + billing_key as param. Please also read above the two new fixes.

Ok then, I'm going to have some hard time now with the release... The Windows Store release is such a big pain if app is multilingual :-\ Hundreds of screenshots, descriptions and redundant data to post manually. They don't make life easy to us :-)

rafaellop commented 8 years ago

@dkarzon There's an easy fix to the test mode issues. The test mode should be enabled in the init function not by a separate call. I did it this way:

store-windows.js

var store = {};

store.verbosity = 0;
store.winstoretestmode = false; // **ADDED LINE**

store-windows.js

InAppBilling.prototype.init = function(success, fail, options, skus) {
        if (!options) options = {};
        this.options = {
            showLog: options.showLog !== false
        };
        if (this.options.showLog) {
            log("setup ok");
        }
        var hasSKUs = false;
        if (typeof skus !== "undefined") {
            if (typeof skus === "string") {
                skus = [ skus ];
            }
            if (skus.length > 0) {
                if (typeof skus[0] !== "string") {
                    var msg = "invalid productIds: " + JSON.stringify(skus);
                    if (this.options.showLog) {
                        log(msg);
                    }
                    fail(msg, store.ERR_INVALID_PRODUCT_ID);
                    return;
                }
                if (this.options.showLog) {
                    log("load " + JSON.stringify(skus));
                }
                hasSKUs = true;
            }
        }
        if (hasSKUs) {
            // ** ADDED THE TESTMODE PARAM (skus isn't used in the target function) **
            cordova.exec(success, errorCb(fail), "InAppBillingPlugin", "init", [ store.winstoretestmode, skus ]);  
        } else {
            // ** ADDED TESTMODE PARAM **
            cordova.exec(success, errorCb(fail), "InAppBillingPlugin", "init", [ store.winstoretestmode ]);
        }
    };

InAppPurchaseProxy.js

init: function (win, fail, args) {

        var testmode = args[0]; // ** ADDED: check if the testmode is on**

        if (!this.currentApp) {
            if (!testmode) { // ** ADDED **
                this.currentApp = Windows.ApplicationModel.Store.CurrentApp;
                win(true);
            } else { // ** ADDED SIMULATOR MODE INITIALIZATION **
                this.currentApp = Windows.ApplicationModel.Store.CurrentAppSimulator; 
                  Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync("www").done(
                    function (folder) {
                          folder.getFileAsync("in-app-purchase.xml").done(
                              function (file) {
                                  console.log("got the xml file for currentAppSimulator", file);
                                  this.currentApp.reloadSimulatorAsync(file).done(
                                      function () {
                                          // Get the license info
                                          this.productLicenses = this.currentApp.licenseInformation.productLicenses;
                                          if (this.currentApp && this.productLicenses) {
                                              console.log("loaded xml file");
                                              win(true);
                                          } else {
                                              console.log("failed xml file load");
                                              fail("failed xml file load");
                                          }
                                      },
                                      function (err) {
                                          console.log("This is still not working!!");
                                          console.log(err);
                                          fail(err);
                                      }
                                  );
                              });
                      }
                  );
            }
        }
    },

After this changes it is enough to set the test mode same way like the debug mode is set and it loads the XML before final initialization and it simply works. Example of test mode init:

        store.verbosity = store.QUIET;
        store.winstoretestmode = true;
        store.ready(function() {
            console.log("\\o/ STORE READY \\o/");
        });
dkarzon commented 8 years ago

@rafaellop Why did you have to move the test xml loading to the init function?

rafaellop commented 8 years ago

@dkarzon I've described this in the thread in details. If the test mode is called after init there is a lot of troubles like not refreshed products list, but the main issue is that this solution simply works properly while the previous completely not.

webbprofessor commented 8 years ago

I made three separate app submissions this weekend hoping to finally get IAP working on Windows Phone, but no luck.

Here are the methods that I updated per this thread: getAvailableProducts() [InAppPurchaseProxy.js] getPurchases() [InAppPurchaseProxy.js] iabGetPurchases() [store-windows.js]

screenshot 2015-10-05 12 16 23

My IAP related config.xml updates: < access origin="https://api.fovea.cc:1982/check-purchase" /> < vs:plugin name="cc.fovea.cordova.purchase" version="4.0.0" />

rafaellop commented 8 years ago

What is your problem? Any errors? Remember that in order to use IAP without simulator you must publish your app (as public or beta). If you try to run IAP capable app on your device without installing it from the Windows Store first, you will always have WinRT errors. I've just published my app and it is working correctly with simulator and with the real store as well. No troubles so far.

BTW. Visual Studio reinstalls plugins from git each time a project is rebuilt. Make sure that you have your changes in the version that you publish because I've got my changes overwritten until I changed the plugin installation source to a local folder (or local git).

webbprofessor commented 8 years ago

Yes, all submissions were publicly available on the store. I'm using cordova-cli 5.1.1 (taco.json)

I just moved the IAP plugin & mentioned revisions to a private git repository -- trying again..

j3k0 commented 8 years ago

That would be nice to contribute with a Pull Request including the fix, especially if you already apply the change in a fork of this repo.

webbprofessor commented 8 years ago

My last store submission still did not fix the issue...there must be something else going on. Is there a way to display IAP logs in-app so i can see if there are any helpful errors? @rafaellop please confirm you did not make any changes to getProductDetails()

@j3k0 the requested Pull Request was submitted.

j3k0 commented 8 years ago

Thanks @webbprofessor for #304, seems like I should wait for confirmation that it's working before merging?

webbprofessor commented 8 years ago

@j3k0 considering that in the current release Windows Phone IAP is completely unusable, I would not wait for this confirmation. This merge will also enable @rafaellop to verify my issues are unrelated to the updated resources.

Note, the current request only addresses in-store apps. Either rafaellop or I will supply the Windows IAP test-mode fix in a separate pull request.

rafaellop commented 8 years ago

@webbprofessor getProductDetails had no influence on my successful usage of the plugin. It worked correctly allowing do to a purchase (real) before I modified it which I finally did. You still didn't say what is the problem with your app. Do you have any errors or something that I could refer to?

@j3k0 What I can do is a fork of the project and including my changes on it. The changes were tested on Windows Phone 8.1 and Windows 10 desktop and both the sim and real purchases works. So, should I do this and copy the above described changes to a new fork?

j3k0 commented 8 years ago

@rafaellop Would be helpful! This way you'll get the credit for this fix in the project. A general note for this (and I hope other) contributions:

Concerning store.winstoretestmode, I'd rather pass it as an option to store.init() and call it something like sandbox (so the same boolean can be used for all platforms to know they're running in test mode). iOS and Android refer to that mode as sandbox. So plugin user do: store.init(success, error, { sandbox: true }); → same for all platforms.

Also, you need to make sure the fix doesn't break the android platform. (plugin-bridge.js is shared by android and windows).

dkarzon commented 8 years ago

@rafaellop @webbprofessor I have found the issue, are you able to try the plugin from my repo and check its working for you? I have used some of your changes in the fix but the main problem was the setProductData function was crashing for non-consumable products.

I have left the setTestMode function as is for now to keep it separate from init.

Also I have included some docs for testing durables in windows.md and I have tested these working with the simulator. Sorry it took so long.

rafaellop commented 8 years ago

My modified fork is here: https://github.com/rafaellop/cordova-plugin-purchase It works for me from scratch.

@dkarzon I'm quite busy with my other projects at the moment. I'll try to verify your fork when I find some time. I have lost a few days on fighting with Windows port and have some arrears...

rafaellop commented 8 years ago

@j3k0 I'll try to move the changes to the js files in the src folder. At this time the www/store-windows.js is manually edited to speed up things and make the plugin work. I've got the message about Android/iOS awarness. Don't worry.

Regarding the store.winstoretestmode. Me, I don't use the store.init and I think most of the users of the plugin also don't. I'm not sure if I'll be able to do this correctly because even on Android I also don't use sandbox and rather the google test account.