in-app-purchase
Version:
In-App-Purchase validation and subscription management for iOS, Android, Amazon, and Windows
366 lines (336 loc) • 11.9 kB
JavaScript
;
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();
};