UNPKG

in-app-purchase-multiple-iec

Version:

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

442 lines (409 loc) 13.4 kB
"use strict"; var async = require("./lib/async"); var AppleModule = require("./lib/apple"); var GoogleModule = require("./lib/google"); var windows = require("./lib/windows"); var amazonManager = require("./lib/amazonManager"); var FacebookModule = require("./lib/facebook"); var roku = require("./lib/roku"); var constants = require("./constants"); var verbose = require("./lib/verbose"); var IS_WINDOWS = "</Receipt>"; function IAPInstance() { this.amazon = null; this.google = new GoogleModule(); this.apple = new AppleModule(); this.facebook = new FacebookModule(); } IAPInstance.prototype.handlePromisedFunctionCb = function (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); }; }; IAPInstance.prototype.config = function (configIn) { this.apple.readConfig(configIn); this.google.readConfig(configIn); windows.readConfig(configIn); this.amazon = amazonManager.create(configIn); this.facebook.readConfig(configIn); roku.readConfig(configIn); verbose.setup(configIn); }; // services const SERVICES = { UNITY: "unity", APPLE: "apple", GOOGLE: "google", WINDOWS: "windows", AMAZON: "amazon", ROKU: "roku", FACEBOOK: "facebook", }; const UNITY = { APPLE: "AppleAppStore", GOOGLE: "GooglePlay", AMAZON: "AmazonApps", }; // validation const VALIDATION = { SUCCESS: 0, FAILURE: 1, POSSIBLE_HACK: 2, }; IAPInstance.prototype.setup = function (cb) { if (!cb && Promise) { return new Promise((resolve, reject) => { this.setup(this.handlePromisedFunctionCb(resolve, reject)); }); } async.series( [ (next) => this.apple.setup(next), (next) => this.google.setup(next), (next) => this.amazon.setup(next), (next) => this.facebook.setup(next), ], cb ); }; IAPInstance.prototype.getService = function (receipt) { if (!receipt) { throw new Error("Receipt was null or undefined"); } if (receipt.indexOf && receipt.indexOf(IS_WINDOWS) !== -1) { return SERVICES.WINDOWS; } if (typeof receipt === "object") { // receipt could be either Google, Amazon, or Unity (Apple or Google or Amazon) if (isUnityReceipt(receipt)) { return SERVICES.UNITY; } if (receipt.signature) { return SERVICES.GOOGLE; } else if (receipt.purchaseToken) { return SERVICES.GOOGLE; } else { return SERVICES.AMAZON; } } if (typeof receipt === "string") { var characters = receipt.match(/\w/g) || ""; var dashes = receipt.match(/-/g) || ""; if (characters.length === 32 && dashes.length === 4) { return SERVICES.ROKU; } } try { // receipt could be either Google, Amazon, or Unity (Apple or Google or Amazon) var parsed = JSON.parse(receipt); if (isUnityReceipt(parsed)) { return SERVICES.UNITY; } if (parsed.signature) { return SERVICES.GOOGLE; } else if (receipt.purchaseToken) { return SERVICES.GOOGLE; } else { return SERVICES.AMAZON; } } catch (error) { var dotSplitedReceipt = receipt.split("."); if (dotSplitedReceipt.length === 2) { return SERVICES.FACEBOOK; } return SERVICES.APPLE; } }; IAPInstance.prototype.validate = function (service, receipt, cb) { if (receipt === undefined && cb === undefined) { // we are given 1 argument as: const promise = .validate(receipt) receipt = service; service = this.getService(receipt); console.log(service); } if (cb === undefined && typeof receipt === "function") { // we are given 2 arguments as: .validate(receipt, cb) cb = receipt; receipt = service; service = this.getService(receipt); } if (!cb && Promise) { return new Promise((resolve, reject) => { this.validate( service, receipt, this.handlePromisedFunctionCb(resolve, reject) ); }); } if (service === SERVICES.UNITY) { service = getServiceFromUnityReceipt(receipt); receipt = parseUnityReceipt(receipt); } if (!service) { return cb(new Error("invalid service given: null")); } switch (service) { case SERVICES.APPLE: this.apple.validatePurchase(null, receipt, cb); break; case SERVICES.GOOGLE: this.google.validatePurchase(null, receipt, cb); break; case SERVICES.WINDOWS: windows.validatePurchase(receipt, cb); break; case SERVICES.AMAZON: this.amazon.validatePurchase(null, receipt, cb); break; case SERVICES.FACEBOOK: this.facebook.validatePurchase(null, receipt, cb); break; case SERVICES.ROKU: roku.validatePurchase(null, receipt, cb); break; default: return cb(new Error("invalid service given: " + service)); } }; IAPInstance.prototype.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 = this.getService(receipt); } if (cb === undefined && typeof receipt === "function") { // we are given 3 arguments as: .validateOnce(receipt, secretPubKey, cb) cb = receipt; receipt = service; service = this.getService(receipt); } if (!cb && Promise) { return new Promise((resolve, reject) => { this.validateOnce( service, secretOrPubKey, receipt, this.handlePromisedFunctionCb(resolve, reject) ); }); } if (service === SERVICES.UNITY) { service = getServiceFromUnityReceipt(receipt); receipt = parseUnityReceipt(receipt); } if ( !secretOrPubKey && service !== SERVICES.APPLE && service !== SERVICES.WINDOWS ) { verbose.log("<.validateOnce>", service, receipt); return cb( new Error( "missing secret or public key for dynamic validation:" + service ) ); } switch (service) { case SERVICES.APPLE: this.apple.validatePurchase(secretOrPubKey, receipt, cb); break; case SERVICES.GOOGLE: this.google.validatePurchase(secretOrPubKey, receipt, cb); break; case SERVICES.WINDOWS: windows.validatePurchase(receipt, cb); break; case SERVICES.AMAZON: this.amazon.validatePurchase(secretOrPubKey, receipt, cb); break; case SERVICES.FACEBOOK: this.facebook.validatePurchase(secretOrPubKey, receipt, cb); break; case SERVICES.ROKU: roku.validatePurchase(secretOrPubKey, receipt, cb); break; default: verbose.log("<.validateOnce>", secretOrPubKey, receipt); return cb(new Error("invalid service given: " + service)); } }; IAPInstance.prototype.isValidated = function (response) { if (response && response.status === constants.VALIDATION.SUCCESS) { return true; } return false; }; IAPInstance.prototype.isExpired = 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; } if (!purchasedItem.expirationDate) { // there is no expiration date with this item return false; } if (Date.now() - purchasedItem.expirationDate >= 0) { return true; } // has not expired yet return false; }; IAPInstance.prototype.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; }; IAPInstance.prototype.getPurchaseData = function (purchaseData, options) { if (!purchaseData || !purchaseData.service) { return null; } switch (purchaseData.service) { case SERVICES.APPLE: return this.apple.getPurchaseData(purchaseData, options); case SERVICES.GOOGLE: return this.google.getPurchaseData(purchaseData, options); case SERVICES.WINDOWS: return windows.getPurchaseData(purchaseData, options); case SERVICES.AMAZON: return this.amazon.getPurchaseData(purchaseData, options); case SERVICES.FACEBOOK: return this.facebook.getPurchaseData(purchaseData, options); case SERVICES.ROKU: return roku.getPurchaseData(purchaseData, options); default: return null; } }; IAPInstance.prototype.refreshGoogleToken = function (cb) { if (!cb && Promise) { return new Promise((resolve, reject) => { this.refreshGoogleToken( this.handlePromisedFunctionCb(resolve, reject) ); }); } this.google.refreshToken(cb); }; IAPInstance.prototype.setAmazonValidationHost = function (vhost) { if (this.amazon.setValidationHost) { return this.amazon.setValidationHost(vhost); } return false; }; IAPInstance.prototype.resetAmazonValidationHost = function () { if (this.amazon.resetValidationHost) { return this.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 SERVICES.GOOGLE; case constants.UNITY.APPLE: return SERVICES.APPLE; case constants.UNITY.AMAZON: return SERVICES.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 this.google.reset(); }; IAPInstance.prototype.switchEnv = function (isSanbox) { // update apple config this.apple.switchEnv(isSanbox); }; IAPInstance.prototype.getEnvApple = function () { return this.apple.getEnvApple(); }; module.exports = function () { return new IAPInstance(); };