UNPKG

in-app-purchase

Version:

In-App-Purchase validation and subscription management for iOS, Android, Amazon, and Windows

366 lines (336 loc) 11.9 kB
'use strict'; var async = require('./lib/async'); var apple = require('./lib/apple'); var google = require('./lib/google'); var windows = require('./lib/windows'); var amazonManager = require('./lib/amazonManager'); var roku = require('./lib/roku'); var constants = require('./constants'); var verbose = require('./lib/verbose'); var IS_WINDOWS = '<\/Receipt>'; var amazon; function handlePromisedFunctionCb(resolve, reject) { return function _handlePromisedCallback(error, response) { if (error) { var errorData = { error: error, status: null, message: null }; if (response !== null && typeof response === 'object') { errorData.status = response.status; errorData.message = response.message; } return reject(JSON.stringify(errorData), response); } return resolve(response); }; } module.exports.UNITY = constants.SERVICES.UNITY; module.exports.APPLE = constants.SERVICES.APPLE; module.exports.GOOGLE = constants.SERVICES.GOOGLE; module.exports.WINDOWS = constants.SERVICES.WINDOWS; module.exports.AMAZON = constants.SERVICES.AMAZON; module.exports.ROKU = constants.SERVICES.ROKU; module.exports.config = function (configIn) { apple.readConfig(configIn); google.readConfig(configIn); windows.readConfig(configIn); amazon = amazonManager.create(configIn); roku.readConfig(configIn); verbose.setup(configIn); }; module.exports.setup = function (cb) { if (!cb && Promise) { return new Promise(function (resolve, reject) { module.exports.setup(handlePromisedFunctionCb(resolve, reject)); }); } async.series([ function (next) { apple.setup(next); }, function (next) { google.setup(next); }, function (next) { amazon.setup(next); } ], cb); }; module.exports.getService = function (receipt) { if (!receipt) { throw new Error('Receipt was null or undefined'); } if (receipt.indexOf && receipt.indexOf(IS_WINDOWS) !== -1) { return module.exports.WINDOWS; } if (typeof receipt === 'object') { // receipt could be either Google, Amazon, or Unity (Apple or Google or Amazon) if (isUnityReceipt(receipt)) { return module.exports.UNITY; } if (receipt.signature) { return module.exports.GOOGLE; } else if (receipt.purchaseToken) { return module.exports.GOOGLE; } else { return module.exports.AMAZON; } } if (typeof receipt === 'string') { var characters = receipt.match(/\w/g) || ''; var dashes = receipt.match(/-/g) || ''; if (characters.length === 32 && dashes.length === 4) { return module.exports.ROKU; } } try { // receipt could be either Google, Amazon, or Unity (Apple or Google or Amazon) var parsed = JSON.parse(receipt); if (isUnityReceipt(parsed)) { return module.exports.UNITY; } if (parsed.signature) { return module.exports.GOOGLE; } else if (receipt.purchaseToken) { return module.exports.GOOGLE; } else { return module.exports.AMAZON; } } catch (error) { return module.exports.APPLE; } }; module.exports.validate = function (service, receipt, cb) { if (receipt === undefined && cb === undefined) { // we are given 1 argument as: const promise = .validate(receipt) receipt = service; service = module.exports.getService(receipt); } if (cb === undefined && typeof receipt === 'function') { // we are given 2 arguments as: .validate(receipt, cb) cb = receipt; receipt = service; service = module.exports.getService(receipt); } if (!cb && Promise) { return new Promise(function (resolve, reject) { module.exports.validate( service, receipt, handlePromisedFunctionCb(resolve, reject) ); }); } if (service === module.exports.UNITY) { service = getServiceFromUnityReceipt(receipt); receipt = parseUnityReceipt(receipt); } switch (service) { case module.exports.APPLE: apple.validatePurchase(null, receipt, cb); break; case module.exports.GOOGLE: google.validatePurchase(null, receipt, cb); break; case module.exports.WINDOWS: windows.validatePurchase(receipt, cb); break; case module.exports.AMAZON: amazon.validatePurchase(null, receipt, cb); break; case module.exports.ROKU: roku.validatePurchase(null, receipt, cb); break; default: return cb(new Error('invalid service given: ' + service)); } }; module.exports.validateOnce = function (service, secretOrPubKey, receipt, cb) { if (receipt === undefined && cb === undefined) { // we are given 2 arguments as: const promise = .validateOnce(receipt, secretOrPubKey) receipt = service; service = module.exports.getService(receipt); } if (cb === undefined && typeof receipt === 'function') { // we are given 3 arguemnts as: .validateOnce(receipt, secretPubKey, cb) cb = receipt; receipt = service; service = module.exports.getService(receipt); } if (!cb && Promise) { return new Promise(function (resolve, reject) { module.exports.validateOnce( service, secretOrPubKey, receipt, handlePromisedFunctionCb(resolve, reject) ); }); } if (service === module.exports.UNITY) { service = getServiceFromUnityReceipt(receipt); receipt = parseUnityReceipt(receipt); } if (!secretOrPubKey && service !== module.exports.APPLE && service !== module.exports.WINDOWS) { verbose.log('<.validateOnce>', service, receipt); return cb(new Error('missing secret or public key for dynamic validation:' + service)); } switch (service) { case module.exports.APPLE: apple.validatePurchase(secretOrPubKey, receipt, cb); break; case module.exports.GOOGLE: google.validatePurchase(secretOrPubKey, receipt, cb); break; case module.exports.WINDOWS: windows.validatePurchase(receipt, cb); break; case module.exports.AMAZON: amazon.validatePurchase(secretOrPubKey, receipt, cb); break; case module.exports.ROKU: roku.validatePurchase(secretOrPubKey, receipt, cb); break; default: verbose.log('<.validateOnce>', secretOrPubKey, receipt); return cb(new Error('invalid service given: ' + service)); } }; module.exports.isValidated = function (response) { if (response && response.status === constants.VALIDATION.SUCCESS) { return true; } return false; }; module.exports.isExpired = function (purchasedItem) { if (!purchasedItem || !purchasedItem.transactionId) { throw new Error('invalid purchased item given:\n' + JSON.stringify(purchasedItem)); } if (!purchasedItem.expirationDate) { // there is no exipiration date with this item return false; } if (Date.now() - purchasedItem.expirationDate >= 0) { return true; } // has not exipired yet return false; }; module.exports.isCanceled = function (purchasedItem) { if (!purchasedItem || !purchasedItem.transactionId) { throw new Error('invalid purchased item given:\n' + JSON.stringify(purchasedItem)); } if (purchasedItem.cancellationDate) { // it has been cancelled return true; } return false; }; module.exports.getPurchaseData = function (purchaseData, options) { if (!purchaseData || !purchaseData.service) { return null; } switch (purchaseData.service) { case module.exports.APPLE: return apple.getPurchaseData(purchaseData, options); case module.exports.GOOGLE: return google.getPurchaseData(purchaseData, options); case module.exports.WINDOWS: return windows.getPurchaseData(purchaseData, options); case module.exports.AMAZON: return amazon.getPurchaseData(purchaseData, options); case module.exports.ROKU: return roku.getPurchaseData(purchaseData, options); default: return null; } }; module.exports.refreshGoogleToken = function (cb) { if (!cb && Promise) { return new Promise(function (resolve, reject) { module.exports.refreshGoogleToken(handlePromisedFunctionCb(resolve, reject)); }); } google.refreshToken(cb); }; module.exports.setAmazonValidationHost = function (vhost) { if (amazon.setValidationHost) { return amazon.setValidationHost(vhost); } return false; }; module.exports.resetAmazonValidationHost = function () { if (amazon.resetValidationHost) { return amazon.resetValidationHost(); } return false; }; function isUnityReceipt(receipt) { if (receipt.Store) { if ( receipt.Store === constants.UNITY.GOOGLE || receipt.Store === constants.UNITY.APPLE || receipt.Store === constants.UNITY.AMAZON ) { return true; } } return false; } function getServiceFromUnityReceipt(receipt) { if (typeof receipt !== 'object') { // at this point we have already established the fact that receipt is a valid JSON string receipt = JSON.parse(receipt); } switch (receipt.Store) { case constants.UNITY.GOOGLE: return module.exports.GOOGLE; case constants.UNITY.APPLE: return module.exports.APPLE; case constants.UNITY.AMAZON: return module.exports.AMAZON; } // invalid Store value return null; } function parseUnityReceipt(receipt) { verbose.log('Parse Unity receipt as ' + receipt.Store); if (typeof receipt !== 'object') { // at this point we have already established the fact that receipt is a valid JSON string receipt = JSON.parse(receipt); } switch (receipt.Store) { case constants.UNITY.GOOGLE: if (typeof receipt.Payload === 'string') { try { receipt.Payload = JSON.parse(receipt.Payload); } catch (error) { throw error; } } var payloadContent = typeof receipt.Payload.json !== 'object' ? JSON.parse(receipt.Payload.json) : receipt.Payload.json; return { data: receipt.Payload.json, signature: receipt.Payload.signature, // add field necessary to use google service account packageName: payloadContent.packageName, productId: payloadContent.productId, purchaseToken: payloadContent.purchaseToken, subscription: (receipt.Subscription !== undefined && receipt.Subscription) }; case constants.UNITY.AMAZON: if (typeof receipt.Payload === 'string') { try { receipt.Payload = JSON.parse(receipt.Payload); } catch (error) { throw error; } } return receipt.Payload; case constants.UNITY.APPLE: return receipt.Payload; } } // test use only module.exports.reset = function () { // resets google setup google.reset(); };