in-app-purchase-iec-develop
Version:
In-App-Purchase validation and subscription management for iOS, Android, Amazon, and Windows
168 lines (159 loc) • 5.78 kB
JavaScript
'use strict';
/**
* Uses Google API and the receipt only requies purchaseToken string to validate
*
*/
var jwt = require('jwt-simple');
var util = require('util');
var request = require('request');
var constants = require('../constants');
var verbose = require('./verbose');
function GoogleAPIModule() {
this.NAME = 'GOOGLE API';
this.GET_TOKEN = 'https://accounts.google.com/o/oauth2/token';
this.SCOPE = 'https://www.googleapis.com/auth/androidpublisher';
this.PRODUCT_VAL = 'https://www.googleapis.com/androidpublisher/v3/applications/%s/purchases/products/%s/tokens/%s?access_token=%s';
this.SUBSCR_VAL = 'https://www.googleapis.com/androidpublisher/v3/applications/%s/purchases/subscriptions/%s/tokens/%s?access_token=%s';
this.conf = {
clientEmail: null,
privateKey: null
};
}
GoogleAPIModule.prototype.config = function (_conf) {
if (!_conf.clientEmail) {
throw new Error('Google API requires client email');
}
if (!_conf.privateKey) {
throw new Error('Google API requires private key');
}
this.conf.clientEmail = _conf.clientEmail;
this.conf.privateKey = _conf.privateKey;
};
GoogleAPIModule.prototype.validatePurchase = function (_googleServiceAccount, receipt, cb) {
verbose.log(this.NAME, 'Validate this', receipt);
if (!receipt.packageName) {
return cb(new Error('Missing Package Name'), {
status: constants.VALIDATION.FAILURE,
message: 'Missing Package Name',
data: receipt
});
} else if (!receipt.productId) {
return cb(new Error('Missing Product ID'), {
status: constants.VALIDATION.FAILURE,
message: 'Missing Product ID',
data: receipt
});
} else if (!receipt.purchaseToken) {
return cb(new Error('Missing Purchase Token'), {
status: constants.VALIDATION.FAILURE,
message: 'Missing Purchase Token',
data: receipt
});
}
var googleServiceAccount = this.conf;
if (_googleServiceAccount && _googleServiceAccount.clientEmail && _googleServiceAccount.privateKey) {
verbose.log(this.NAME, 'Using one time key data:', _googleServiceAccount);
googleServiceAccount = _googleServiceAccount;
}
this._getToken(googleServiceAccount.clientEmail, googleServiceAccount.privateKey, function (error, token) {
if (error) {
return cb(error, {
status: constants.VALIDATION.FAILURE,
message: error.message
});
}
var url = this._getValidationUrl(receipt, token);
verbose.log(this.NAME, 'Validation URL:', url);
var params = {
method: 'GET',
url: url,
json: true
};
request(params, function (error, res, body) {
if (error) {
return cb(error, { status: constants.VALIDATION.FAILURE, message: body });
}
if (res.statusCode === 410) {
verbose.log(this.NAME, 'Receipt is no longer valid');
return cb(new Error('ReceiptNoLongerValid'), {
status: constants.VALIDATION.FAILURE,
message: body
});
}
if (res.statusCode > 399) {
verbose.log(this.NAME, 'Validation failed:', res.statusCode, body);
var msg;
try {
msg = JSON.stringify(body, null, 2);
} catch (e) {
msg = body;
}
return cb(new Error('Status:' + res.statusCode + ' - ' + msg), {
status: constants.VALIDATION.FAILURE,
message: body,
data: receipt
});
}
var resp = {
service: constants.SERVICES.GOOGLE,
status: constants.VALIDATION.SUCCESS,
packageName: receipt.packageName,
productId: receipt.productId,
purchaseToken: receipt.purchaseToken
};
for (var name in body) {
resp[name] = body[name];
}
cb(null, resp);
});
}.bind(this));
};
GoogleAPIModule.prototype._getToken = function (clientEmail, privateKey, cb) {
var now = Math.floor(Date.now() / 1000);
var token = jwt.encode({
iss: clientEmail,
scope: this.SCOPE,
aud: this.GET_TOKEN,
exp: now + 3600,
iat: now
}, privateKey, 'RS256');
var params = {
method: 'POST',
url: this.GET_TOKEN,
body: 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=' + token,
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
json: true
};
verbose.log(this.NAME, 'Get token with', clientEmail, '\n', privateKey);
request(params, function (error, res, body) {
if (error) {
return cb(error);
}
if (res.statusCode > 399) {
return cb(new Error('Failed to get token: ' + body));
}
cb(null, body.access_token);
});
};
GoogleAPIModule.prototype._getValidationUrl = function (receipt, token) {
var url = '';
switch (receipt.subscription) {
case true:
url = this.SUBSCR_VAL;
break;
case false:
default:
url = this.PRODUCT_VAL;
break;
}
return util.format(
url,
encodeURIComponent(receipt.packageName),
encodeURIComponent(receipt.productId),
encodeURIComponent(receipt.purchaseToken),
encodeURIComponent(token)
);
};
module.exports = GoogleAPIModule;