5

I'm trying to convert my Google Chrome Extension, which is published in the Chrome Web Store, into a Free Trial using their new Licensing API - however Googles docs for this are excruciatingly confusing to me. See: https://developer.chrome.com/webstore/check_for_payment

Furthermore, it seems that OpenID 2.0 is deprecated? https://developers.google.com/accounts/docs/OpenID2

Is there some sort of drop-in code to setup a Free Trial and check the user against the Licensing API? I have a lot of users and I don't want to mess up and force them to hit a payment wall- they should be grandfathered in for free. I can't find anyone else online who has done this to look at their code and understand.

Ideally my extension should be fully functional for 7 days, and then expire the free trial and require payment from the user.

I appreciate any help here!

Catalyst
  • 137
  • 4
  • 9

1 Answers1

7

I found a great resource at https://github.com/GoogleChrome/chrome-app-samples/tree/master/samples/one-time-payment

I edited it a bit to add more error trapping as I moved it into my app. I had a lot of times where I had to go to different Google API things though and edit them. If you have trouble with this code, I will try to look up what I changed and let you know. You can copy and paste this into the console window of the background page and execute it with getLicense. If you copy your key into the manifest, you can do it with a local extension and don't have to wait an hour with every change. I highly recommend that. The key is the one from the developer dashboard -more info.

function getLicense() {
  var CWS_LICENSE_API_URL = 'https://www.googleapis.com/chromewebstore/v1.1/userlicenses/';
  xhrWithAuth('GET', CWS_LICENSE_API_URL + chrome.runtime.id, true, onLicenseFetched);
}

function onLicenseFetched(error, status, response) {
  function extensionIconSettings(badgeColorObject, badgeText, extensionTitle ){
    chrome.browserAction.setBadgeBackgroundColor(badgeColorObject);
    chrome.browserAction.setBadgeText({text:badgeText});
    chrome.browserAction.setTitle({ title: extensionTitle });
  }
  var licenseStatus = "";
  if (status === 200 && response) {
    response = JSON.parse(response);
    licenseStatus = parseLicense(response);
  } else {
    console.log("FAILED to get license. Free trial granted.");
    licenseStatus = "unknown";
  }
  if(licenseStatus){
    if(licenseStatus === "Full"){
      window.localStorage.setItem('ChromeGuardislicensed', 'true');
      extensionIconSettings({color:[0, 0, 0, 0]}, "", "appname is enabled.");
    }else if(licenseStatus === "None"){
      //chrome.browserAction.setIcon({path: icon}); to disabled - grayed out?
      extensionIconSettings({color:[255, 0, 0, 230]}, "?", "appnameis disabled.");
      //redirect to a page about paying as well?
    }else if(licenseStatus === "Free"){
      window.localStorage.setItem('appnameislicensed', 'true');
      extensionIconSettings({color:[255, 0, 0, 0]}, "", window.localStorage.getItem('daysLeftInappnameTrial') + " days left in free trial.");
    }else if(licenseStatus === "unknown"){
      //this does mean that if they don't approve the permissions,
      //it works free forever. This might not be ideal
      //however, if the licensing server isn't working, I would prefer it to work.
      window.localStorage.setItem('appnameislicensed', 'true');
      extensionIconSettings({color:[200, 200, 0, 100]}, "?", "appnameis enabled, but was unable to check license status.");
    }
  }
  window.localStorage.setItem('appnameLicenseCheckComplete', 'true');
}

/*****************************************************************************
* Parse the license and determine if the user should get a free trial
*  - if license.accessLevel == "FULL", they've paid for the app
*  - if license.accessLevel == "FREE_TRIAL" they haven't paid
*    - If they've used the app for less than TRIAL_PERIOD_DAYS days, free trial
*    - Otherwise, the free trial has expired 
*****************************************************************************/

function parseLicense(license) {
  var TRIAL_PERIOD_DAYS = 1;
  var licenseStatusText;
  var licenceStatus;
  if (license.result && license.accessLevel == "FULL") {
    console.log("Fully paid & properly licensed.");
    LicenseStatus = "Full";
  } else if (license.result && license.accessLevel == "FREE_TRIAL") {
    var daysAgoLicenseIssued = Date.now() - parseInt(license.createdTime, 10);
    daysAgoLicenseIssued = daysAgoLicenseIssued / 1000 / 60 / 60 / 24;
    if (daysAgoLicenseIssued <= TRIAL_PERIOD_DAYS) {
      window.localStorage.setItem('daysLeftInCGTrial', TRIAL_PERIOD_DAYS - daysAgoLicenseIssued);
      console.log("Free trial, still within trial period");
      LicenseStatus = "Free";
    } else {
      console.log("Free trial, trial period expired.");
      LicenseStatus = "None";
      //open a page telling them it is not working since they didn't pay?
    }
  } else {
    console.log("No license ever issued.");
    LicenseStatus = "None";
    //open a page telling them it is not working since they didn't pay?
  }
  return LicenseStatus;
}

/*****************************************************************************
* Helper method for making authenticated requests
*****************************************************************************/

// Helper Util for making authenticated XHRs
function xhrWithAuth(method, url, interactive, callback) {
  console.log(url);
  var retry = true;
  var access_token;
  getToken();

  function getToken() {
    console.log("Calling chrome.identity.getAuthToken", interactive);
    chrome.identity.getAuthToken({ interactive: interactive }, function(token) {
      if (chrome.runtime.lastError) {
        callback(chrome.runtime.lastError);
        return;
      }
      console.log("chrome.identity.getAuthToken returned a token", token);
      access_token = token;
      requestStart();
    });
  }

  function requestStart() {
    console.log("Starting authenticated XHR...");
    var xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.setRequestHeader('Authorization', 'Bearer ' + access_token);
    xhr.onreadystatechange = function (oEvent) { 
      if (xhr.readyState === 4) {  
        if (xhr.status === 401 && retry) {
          retry = false;
          chrome.identity.removeCachedAuthToken({ 'token': access_token },
                                                getToken);
        } else if(xhr.status === 200){
          console.log("Authenticated XHR completed.");
          callback(null, xhr.status, xhr.response);
        }
        }else{
          console.log("Error - " + xhr.statusText);  
        }
      }
    try {
      xhr.send();
    } catch(e) {
      console.log("Error in xhr - " + e);
    }
  }
}
Karl Henselin
  • 1,015
  • 12
  • 17
  • Karl, awesome - I'm going to dive into this again tonight and edit this comment to let you know what I find out. On your error- finding your client id should be pretty straightforward - see this post: http://stackoverflow.com/questions/23822170/getting-unique-clientid-from-chrome-extension – Catalyst Sep 21 '14 at 03:55
  • Karl - seems like you may be having this problem where you didn't specify a name- see solution here http://stackoverflow.com/questions/21297148/where-can-i-invoke-chrome-identity-getauthtoken-to-get-a-valid-token – Catalyst Sep 21 '14 at 06:52
  • 2
    I keep getting a ' Cannot read property 'getAuthToken' of undefined ' when calling chrome.identity.getAuthToken--- any ideas? – Catalyst Sep 21 '14 at 06:54
  • I did get it all working on my app. I had to turn some stuff on in the Google API stuff, but if you were already able to get the one time payment working, I don't know why the free trial wouldn't be working for you. – Karl Henselin Sep 21 '14 at 15:46
  • I did edit their sample some. I don't think anything in it is too personal. I will post it. – Karl Henselin Sep 21 '14 at 15:48
  • Thanks for this - got most everything working. I think you should be using chrome.storage.sync instead of localstorage. Quick question- when the Free_TRIAL is up, are the users supposed to go back to the chrome web store to purchase, or can they do it in-app? I.E. Should I show a payment wall with a link the chrome web store – Catalyst Sep 22 '14 at 03:03
  • I am showing a link to the app store purchase in my popup window if they are in free trial mode and a notice it is disabled and the link if it is expired. I really liked the badge stuff that I could do as well. I had to research making the links actually work by opening the href in a new tab, but it worked after that. If you are doing in-app payments for additional features, you might do it a different way. I don't know about that. – Karl Henselin Sep 22 '14 at 13:34
  • also, thanks for the tip about the storage. I will look into that. – Karl Henselin Sep 22 '14 at 13:35
  • 1
    Karl, just curious, how are you grandfathering in users who already installed your app? I'm trying to figure out the best way to do this using the license.createdTime – Catalyst Sep 24 '14 at 04:56
  • Using license.createdtime is what I read as well. I don't have any users yet, and I think I will charge from day one. – Karl Henselin Sep 24 '14 at 15:09
  • 1
    I'm getting an error[1] when I try to get license with the above code. I've followed the same steps mentioned in the answer. Does this mean the google license API has some issues on the server side? [1]: https://i.stack.imgur.com/PtcyK.png – Sunil Kumar Jan 19 '19 at 15:15