UNPKG

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
'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;